SISWA 


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Smart Imtihan - MAN 3 Kuningan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { 
            font-family: 'Plus Jakarta Sans', sans-serif; 
            background: url('https://man3-kuningan.my.id/wp-content/uploads/2025/10/IMG-20251028-WA0041-scaled-e1761638435721.jpg') no-repeat center center fixed;
            background-size: cover;
        }
        .overlay-kaca { background: rgba(255, 255, 255, 0.96); backdrop-filter: blur(12px); }
        .garis-kop-double { border-bottom: 4px double black; margin: 10px 0; }
        .unselectable { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; }
    </style>
</head>
<body class="min-h-screen flex flex-col items-center p-4 unselectable" oncontextmenu="return false;">

    <div class="w-full max-w-3xl">
        <div id="boxLogin" class="overlay-kaca p-8 rounded-[2.5rem] shadow-2xl border-t-[12px] border-green-700 mt-10">
            <div class="text-center mb-8">
                <div class="flex justify-center mb-4">
                    <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-20 h-20 object-contain">
                </div>
                <h1 class="text-3xl font-extrabold text-slate-900 leading-tight">Selamat Datang!</h1>
                <p class="text-lg font-bold text-green-700 uppercase">Smart Imtihan MAN 3 Kuningan</p>
                <p class="text-xs text-slate-500 mt-1 font-semibold">Jujur dalam ujian, mulia dalam prestasi. Created By Mamat Rahmat, Lc</p>
            </div>
            
            <div class="space-y-4">
                <div>
                    <label class="text-xs font-bold text-slate-500 ml-2 mb-1 block uppercase">Nomor Peserta Ujian</label>
                    <input type="text" id="sNomor" placeholder="Contoh: UJ-001" class="w-full p-4 border-2 border-slate-200 rounded-2xl outline-none focus:border-green-600 transition-all font-mono tracking-wider font-bold uppercase">
                </div>

                <div>
                    <label class="text-xs font-bold text-slate-500 ml-2 mb-1 block uppercase">Nama Lengkap</label>
                    <input type="text" id="sNama" placeholder="Nama Lengkap Siswa" class="w-full p-4 border-2 border-slate-200 rounded-2xl outline-none focus:border-green-600 transition-all">
                </div>
                
                <div>
                    <label class="text-xs font-bold text-slate-500 ml-2 mb-1 block uppercase">Kelas</label>
                    <select id="sKelas" class="w-full p-4 border-2 border-slate-200 rounded-2xl outline-none focus:border-green-600 transition-all font-bold">
                        <option value="">-- Pilih Kelas --</option>
                        <optgroup label="KELAS 10">
                            <option value="10-A">10-A</option><option value="10-B">10-B</option><option value="10-C">10-C</option><option value="10-D">10-D</option>
                        </optgroup>
                        <optgroup label="KELAS 11">
                            <option value="11-A">11-A</option><option value="11-B">11-B</option><option value="11-C">11-C</option><option value="11-D">11-D</option>
                        </optgroup>
                        <optgroup label="KELAS 12">
                            <option value="12-A">12-A</option><option value="12-B">12-B</option><option value="12-C">12-C</option><option value="12-D">12-D</option>
                        </optgroup>
                    </select>
                </div>

                <div>
                    <label class="text-xs font-bold text-slate-500 ml-2 mb-1 block uppercase">Mata Pelajaran Ujian (Hari Ini)</label>
                    <select id="mapelOtomatis" onchange="validasiWaktuMapel()" class="w-full p-4 bg-green-50 border-2 border-green-200 rounded-2xl font-black text-green-800 outline-none">
                        <option value="">-- Memuat Mapel Aktif... --</option>
                    </select>
                </div>
                
                <div id="infoJadwalUjian" class="hidden p-4 rounded-xl text-center font-bold text-sm"></div>
                
                <button id="btnMulai" onclick="mulaiUjian()" class="w-full py-5 bg-green-700 text-white rounded-2xl font-black hover:bg-green-800 active:scale-95 transition-all uppercase mt-4 text-lg shadow-lg">
                    MULAI KERJAKAN
                </button>
            </div>
        </div>

        <div id="boxUjian" class="hidden space-y-6 pb-24 mt-4">
            <div class="bg-white p-6 rounded-[2.5rem] shadow-lg border border-slate-200 sticky top-2 z-50 overlay-kaca">
                <div class="flex items-center justify-between gap-4">
                    <img src="https://man3-kuningan.my.id/wp-content/uploads/2026/01/BackgroundEraser_20240730_143709535.png" class="w-12 h-12 object-contain">
                    <div class="text-center flex-1">
                        <h2 class="text-[10px] font-bold uppercase tracking-widest">Kementerian Agama Republik Indonesia</h2>
                        <h1 class="text-lg font-black text-green-800 uppercase leading-none">MAN 3 KUNINGAN</h1>
                        <p class="text-[8px] mt-1 font-medium italic">"Membangun Generasi Cerdas dan Berakhlak Karima"</p>
                        <div class="garis-kop-double"></div>
                    </div>
                    <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-12 h-12 object-contain">
                </div>
                
                <div class="grid grid-cols-2 gap-2 text-xs border-b border-slate-100 pb-3 mb-3">
                    <div>
                        <span class="text-slate-400 block font-bold uppercase text-[9px]">Nama Peserta</span>
                        <span id="lblNamaSiswa" class="font-bold text-slate-800 text-sm">-</span>
                    </div>
                    <div>
                        <span class="text-slate-400 block font-bold uppercase text-[9px]">Nomor Ujian / Kelas</span>
                        <span id="lblNoKelas" class="font-mono font-bold text-slate-800 text-sm">-</span>
                    </div>
                    <div>
                        <span class="text-slate-400 block font-bold uppercase text-[9px]">Mata Pelajaran</span>
                        <span id="lblMapel" class="font-bold text-green-700 text-sm">-</span>
                    </div>
                    <div class="bg-red-50 p-2 rounded-xl border border-red-100 flex flex-col justify-center items-center">
                        <span class="text-red-500 block font-black uppercase text-[9px] tracking-wider">SISA WAKTU</span>
                        <span id="lblTimer" class="font-mono font-black text-red-600 text-lg leading-none mt-0.5">00:00:00</span>
                    </div>
                </div>

                <div id="statusAntiCheat" class="text-center text-[10px] text-red-600 font-bold uppercase hidden animate-pulse">⚠️ Sistem Keamanan Layar & Anti-Cheat Aktif</div>
            </div>

            <div id="wadahSoal" class="space-y-4 pt-2"></div>
            
            <div class="sticky bottom-4 px-2">
                <button id="btnKirimUjian" onclick="kirimJawabanFinal(false)" class="w-full py-6 bg-green-700 text-white rounded-[2rem] font-black text-xl hover:bg-green-800 shadow-2xl active:scale-95 transition-all uppercase border-4 border-white">
                    Selesai & Kirim Jawaban
                </button>
            </div>
        </div>
    </div>

    <script>
        const API_URL = "api.php"; 
        let dataSoal = [];
        let mapelTerpilih = "";
        let guruPemilik = "";
        let jumlahPelanggaran = 0;
        let riwayatPelanggaran = [];
        let isExamStarted = false;
        let antiCheatSetting = 0; 
        let isSubmitting = false; 
        
        let totalDetikMundur = 0;
        let intervalTimer = null;
        let intervalCekWaktu = null;

        window.onblur = null;
        window.onload = muatDaftarMapel;

        function hitungMenitMurni(stringJam) {
            if(!stringJam || typeof stringJam !== 'string') return 0;
            const part = stringJam.split(':');
            if(part.length < 2) return 0;
            return (parseInt(part[0], 10) * 60) + parseInt(part[1], 10);
        }

        async function dapatkanWaktuServerSekarang() {
            try {
                const res = await fetch(`${API_URL}?action=get_server_time`);
                const data = await res.json();
                if(data && data.time) return data.time; 
            } catch(e) {}
            const d = new Date();
            return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
        }

        async function muatDaftarMapel() {
            try {
                const jamServerStr = await dapatkanWaktuServerSekarang();
                const menitServerSekarang = hitungMenitMurni(jamServerStr);

                const res = await fetch(`${API_URL}?action=get_mapel_hari_ini`);
                const list = await res.json();
                
                const sel = document.getElementById('mapelOtomatis');
                sel.innerHTML = '<option value="">-- Pilih Mapel --</option>';
                
                if(!list || list.length === 0) {
                    sel.innerHTML = '<option value="">Tidak ada jadwal ujian aktif hari ini</option>';
                    return;
                }

                let jumlahMapelTampil = 0;

                list.forEach(m => {
                    let jamMulai = m.jam_mulai || "00:00";
                    let durasiMenit = parseInt(m.durasi, 10) || 120;
                    
                    const menitMulaiUjian = hitungMenitMurni(jamMulai);
                    const menitSelesaiUjian = menitMulaiUjian + durasiMenit;

                    if (menitServerSekarang >= menitMulaiUjian && menitServerSekarang <= menitSelesaiUjian) {
                        sel.innerHTML += `<option value="${m.mapel}" data-guru="${m.guru}" data-anticheat="${m.anti_cheat}" data-durasi="${durasiMenit}" data-jammulai="${jamMulai}">${m.mapel} (Mulai Pukul: ${jamMulai.substring(0,5)})</option>`;
                        jumlahMapelTampil++;
                    }
                });

                if (jumlahMapelTampil === 0) {
                    list.forEach(m => {
                        let jamMulai = m.jam_mulai || "00:00";
                        let durasiMenit = parseInt(m.durasi, 10) || 120;
                        sel.innerHTML += `<option value="${m.mapel}" data-guru="${m.guru}" data-anticheat="${m.anti_cheat}" data-durasi="${durasiMenit}" data-jammulai="${jamMulai}">${m.mapel} (Jadwal: ${jamMulai.substring(0,5)})</option>`;
                    });
                }

            } catch (err) {
                document.getElementById('mapelOtomatis').innerHTML = '<option value="">Gagal menyinkronkan jadwal mapel</option>';
            }
        }

        async function validasiWaktuMapel() {
            clearInterval(intervalCekWaktu);
            const sel = document.getElementById('mapelOtomatis');
            const info = document.getElementById('infoJadwalUjian');
            const btn = document.getElementById('btnMulai');

            if (!sel.value) {
                info.classList.add('hidden');
                btn.disabled = false;
                return;
            }

            const opt = sel.options[sel.selectedIndex];
            const jamMulaiStr = opt.dataset.jammulai; 

            info.classList.remove('hidden');
            info.className = "p-4 rounded-xl text-center font-bold text-sm bg-slate-100 text-slate-600 animate-pulse";
            info.innerText = "⏳ Memverifikasi jam rilis server...";

            intervalCekWaktu = setInterval(async () => {
                const jamServerStr = await dapatkanWaktuServerSekarang();
                const menitMulaiUjian = hitungMenitMurni(jamMulaiStr);
                const menitServerSekarang = hitungMenitMurni(jamServerStr);

                if (menitServerSekarang < (menitMulaiUjian - 5)) {
                    btn.disabled = true;
                    btn.className = "w-full py-5 bg-slate-300 text-slate-500 rounded-2xl font-black cursor-not-allowed uppercase mt-4 text-lg shadow-none";
                    info.className = "p-4 rounded-xl text-center font-bold text-sm bg-amber-100 text-amber-800";
                    
                    const sisaMenit = menitMulaiUjian - menitServerSekarang;
                    info.innerText = `🔒 BELUM WAKTUNYA! Jadwal Rilis: Pukul ${jamMulaiStr.substring(0,5)} WIB (Sisa ${Math.floor(sisaMenit/60)} Jam ${sisaMenit%60} Menit)`;
                } else {
                    clearInterval(intervalCekWaktu);
                    btn.disabled = false;
                    btn.className = "w-full py-5 bg-green-700 text-white rounded-2xl font-black hover:bg-green-800 active:scale-95 transition-all uppercase mt-4 text-lg shadow-lg";
                    info.className = "p-4 rounded-xl text-center font-bold text-sm bg-green-100 text-green-800";
                    info.innerText = "✅ WAKTU UJIAN TELAH DIBUKA! Silakan klik tombol di bawah.";
                }
            }, 2500); 
        }

        async function mulaiUjian() {
            const noUjian = document.getElementById('sNomor').value.trim().toUpperCase();
            const nm = document.getElementById('sNama').value.trim();
            const kl = document.getElementById('sKelas').value;
            const sel = document.getElementById('mapelOtomatis');
            
            if(!noUjian || !nm || !kl || !sel.value) return alert("Harap lengkapi No Ujian, Nama, Kelas dan Pilih Mata Pelajaran!");

            const optTerpilih = sel.options[sel.selectedIndex];
            const jamMulaiStr = optTerpilih.dataset.jammulai;

            const jamServerFinal = await dapatkanWaktuServerSekarang();
            if (hitungMenitMurni(jamServerFinal) < (hitungMenitMurni(jamMulaiStr) - 5)) {
                return alert(`🔒 AKSES BELUM DIBUKA!\n\nJam Server saat ini (${jamServerFinal.substring(0,5)}) belum memasuki waktu rilis mapel.`);
            }

            mapelTerpilih = sel.value;
            guruPemilik = optTerpilih.dataset.guru;
            antiCheatSetting = parseInt(optTerpilih.dataset.anticheat, 10) || 0;
            
            let durasiMenit = parseInt(optTerpilih.dataset.durasi, 10) || 90; 
            totalDetikMundur = durasiMenit * 60;

            try {
                const checkRes = await fetch(`${API_URL}?action=get_hasil`);
                const dataHasil = await checkRes.json();
                const dataSiswaLama = dataHasil.find(h => h.nama.startsWith(`[${noUjian}]`) && h.mapel === mapelTerpilih);
                
                if (dataSiswaLama) {
                    if (parseInt(dataSiswaLama.pelanggaran) >= 3) {
                        return alert(`🔒 AKSES DIKUNCI / DIBLOKIR!\n\nHubungi Guru Pengampu (${guruPemilik}).`);
                    }
                    if (dataSiswaLama.skor !== null && parseInt(dataSiswaLama.pelanggaran) < 3) {
                        return alert(`🚫 ANDA SUDAH MENGERJAKAN!\n\nNaskah ujian mapel ini hanya dapat dikerjakan satu kali.`);
                    }
                    jumlahPelanggaran = parseInt(dataSiswaLama.pelanggaran) || 0;
                    try { riwayatPelanggaran = JSON.parse(dataSiswaLama.log_pelanggaran || "[]"); } catch(e){}
                }

                const res = await fetch(`${API_URL}?action=get_soal_siswa&mapel=${encodeURIComponent(mapelTerpilih)}`);
                const data = await res.json();
                
                if(!data || !data.soal) return alert("Naskah soal belum diterbitkan atau kosong!");
                
                if (typeof data.soal === 'string') {
                    dataSoal = JSON.parse(decodeURIComponent(data.soal));
                } else {
                    dataSoal = data.soal;
                }
                
                if(dataSoal.length === 0) return alert("Butir soal kosong!");

                document.getElementById('lblNamaSiswa').innerText = nm.toUpperCase();
                document.getElementById('lblNoKelas').innerText = `${noUjian} / KELAS ${kl}`;
                document.getElementById('lblMapel').innerText = mapelTerpilih;
                
                clearInterval(intervalCekWaktu); 
                document.getElementById('boxLogin').classList.add('hidden');
                document.getElementById('boxUjian').classList.remove('hidden');
                
                isExamStarted = true;
                renderSoalSiswa();
                pulihkanJawabanSiswa(noUjian); 
                mulaiHitungMundurUjian();
                
                if (antiCheatSetting === 1) {
                    document.getElementById('statusAntiCheat').classList.remove('hidden');
                    window.onblur = function() {
                        if(isExamStarted) catatPelanggaranSiswa("Pindah Tab / Membuka Aplikasi Lain");
                    };
                }

            } catch (err) {
                alert("Gagal memproses validasi akun dan naskah ujian.");
            }
        }

        function mulaiHitungMundurUjian() {
            clearInterval(intervalTimer);
            intervalTimer = setInterval(() => {
                if (totalDetikMundur <= 0) {
                    clearInterval(intervalTimer);
                    document.getElementById('lblTimer').innerText = "WAKTU HABIS";
                    alert("⏳ Waktu habis! Jawaban otomatis disimpan.");
                    kirimJawabanFinal(true);
                    return;
                }
                totalDetikMundur--;
                let jam = Math.floor(totalDetikMundur / 3600);
                let menit = Math.floor((totalDetikMundur % 3600) / 60);
                let detik = totalDetikMundur % 60;
                document.getElementById('lblTimer').innerText = `${jam < 10 ? "0"+jam : jam}:${menit < 10 ? "0"+menit : menit}:${detik < 10 ? "0"+detik : detik}`;
            }, 1000);
        }

        function renderSoalSiswa() {
            const wadah = document.getElementById('wadahSoal');
            const noUjian = document.getElementById('sNomor').value.trim().toUpperCase();
            
            wadah.innerHTML = dataSoal.map((s, i) => {
                const cleanTanya = (s.tanya || '').replace(/<br>/g, '\n');
                const isEssay = s.tipe === 'essay' || !s.opsi || s.opsi.length === 0;

                let komponenJawaban = '';

                if (isEssay) {
                    komponenJawaban = `
                    <div class="mt-4">
                        <label class="text-xs font-bold text-slate-500 mb-1 block uppercase">Jawaban Anda:</label>
                        <textarea id="essay_${i}" name="essay_${i}" rows="4" 
                            oninput="simpanJawabanLokal('${noUjian}', ${i}, this.value)"
                            placeholder="Ketik jawaban essay Anda di sini..." 
                            class="w-full p-4 border-2 border-slate-200 rounded-xl outline-none focus:border-green-600 transition-all text-sm text-slate-700"></textarea>
                    </div>`;
                } else {
                    komponenJawaban = `
                    <div class="mt-4 space-y-2">
                        ${s.opsi.map((o, oIdx) => {
                            const labelHuruf = String.fromCharCode(65 + oIdx);
                            return `
                            <label class="flex flex-col p-3 border-2 border-slate-100 rounded-xl cursor-pointer hover:bg-green-50/50 transition-all">
                                <div class="flex items-center gap-3">
                                    <input type="radio" name="q_${i}" value="${labelHuruf}" onchange="simpanJawabanLokal('${noUjian}', ${i}, '${labelHuruf}')" class="w-4 h-4 accent-green-700 shrink-0">
                                    <span class="text-xs font-bold text-green-700">${labelHuruf}.</span>
                                    <span class="text-xs text-slate-700">${(o.teks || '').replace(/\*/g, '')}</span>
                                </div>
                                ${o.gambar ? `<div class="mt-2 ml-7"><img src="${o.gambar}" class="max-h-28 rounded border border-slate-200"></div>` : ''}
                            </label>`;
                        }).join('')}
                    </div>`;
                }

                return `
                <div class="bg-white p-6 rounded-2xl shadow-md border border-slate-200 text-left">
                    <div class="flex justify-between items-center mb-2">
                        <span class="inline-block px-2 py-0.5 bg-slate-100 text-slate-500 rounded text-[10px] font-bold uppercase">SOAL NOMOR ${i+1}</span>
                        <span class="text-[10px] font-extrabold text-indigo-700 bg-indigo-50 px-2 py-0.5 rounded">Bobot Soal: ${s.bobot || 1}</span>
                    </div>
                    <p class="font-bold text-slate-800 text-sm whitespace-pre-line">${cleanTanya}</p>
                    ${s.gambar ? `<div class="my-3 max-w-full"><img src="${s.gambar}" class="max-h-60 rounded-lg border border-slate-200 mx-auto"></div>` : ''}
                    ${komponenJawaban}
                </div>`;
            }).join('');
        }

        function simpanJawabanLokal(noUjian, indexSoal, nilaiJawaban) {
            localStorage.setItem(`si_${noUjian}_${mapelTerpilih}_q_${indexSoal}`, nilaiJawaban);
        }

        function pulihkanJawabanSiswa(noUjian) {
            dataSoal.forEach((s, i) => {
                const jawabanTersimpan = localStorage.getItem(`si_${noUjian}_${mapelTerpilih}_q_${i}`);
                if (jawabanTersimpan) {
                    const isEssay = s.tipe === 'essay' || !s.opsi || s.opsi.length === 0;
                    if (isEssay) {
                        const txtArea = document.getElementById(`essay_${i}`);
                        if (txtArea) txtArea.value = jawabanTersimpan;
                    } else {
                        const rds = document.getElementsByName(`q_${i}`);
                        rds.forEach(r => { if (r.value === jawabanTersimpan) r.checked = true; });
                    }
                }
            });
        }

        function bersihkanJawabanLokal(noUjian) {
            dataSoal.forEach((s, i) => { localStorage.removeItem(`si_${noUjian}_${mapelTerpilih}_q_${i}`); });
        }

        function catatPelanggaranSiswa(jenis) {
            if (parseInt(antiCheatSetting, 10) !== 1) return; 
            jumlahPelanggaran++;
            riwayatPelanggaran.push({ waktu: new Date().toLocaleTimeString(), jenis: jenis });
            
            if(jumlahPelanggaran >= 3) {
                clearInterval(intervalTimer);
                window.onblur = null;
                alert("🔒 BATAS PELANGGARAN TERPENUHI!\nJawaban otomatis dikirim.");
                kirimJawabanFinal(true);
            } else {
                alert(`⚠️ PERINGATAN KE-${jumlahPelanggaran}!\nDilarang meninggalkan layar!`);
                sinkronisasiPelanggaranMenengah();
            }
        }

        async function sinkronisasiPelanggaranMenengah() {
            const noUjian = document.getElementById('sNomor').value.trim().toUpperCase();
            const nm = document.getElementById('sNama').value.trim();
            const fd = new FormData();
            fd.append('nama', `[${noUjian}] ${nm}`);
            fd.append('kelas', document.getElementById('sKelas').value);
            fd.append('mapel', mapelTerpilih);
            fd.append('guru', guruPemilik);
            fd.append('skor', 0); 
            fd.append('pelanggaran', jumlahPelanggaran);
            fd.append('detail_hasil', JSON.stringify([]));
            fd.append('log_pelanggaran', JSON.stringify(riwayatPelanggaran));
            try { await fetch(`${API_URL}?action=simpan_hasil`, { method: 'POST', body: fd }); } catch (e){}
        }

        async function kirimJawabanFinal(otomatis = false) {
            if(!otomatis && !confirm("Apakah Anda yakin ingin menyelesaikan ujian?")) return;
            if(isSubmitting) return; 

            isSubmitting = true;
            clearInterval(intervalTimer);
            clearInterval(intervalCekWaktu);
            window.onblur = null;

            const btn = document.getElementById('btnKirimUjian');
            btn.innerText = "⏳ Memproses Jawaban...";
            btn.disabled = true;

            const noUjian = document.getElementById('sNomor').value.trim().toUpperCase();
            const nm = document.getElementById('sNama').value.trim();

            let totalBobotDidapat = 0;
            let detailHasilArray = []; 

            dataSoal.forEach((s, i) => {
                const bobotSoal = parseFloat(s.bobot) || 0;
                const isEssay = s.tipe === 'essay' || !s.opsi || s.opsi.length === 0;

                if (isEssay) {
                    const txtArea = document.getElementById(`essay_${i}`);
                    let jawabanEssaySiswa = txtArea ? txtArea.value.trim() : "";

                    detailHasilArray.push({
                        id: s.id || (i + 1),
                        tipe: "essay",
                        jawaban_siswa: jawabanEssaySiswa,
                        jawaban_teks: jawabanEssaySiswa,
                        kunci_asli: s.kunci_asli || "",
                        hasil: "MENUNGGU KOREKSI",
                        bobot: bobotSoal,
                        skor_manual: 0 // PERBAIKAN: Pastikan ini angka murni 0 (bukan string) agar input guru bisa diubah
                    });
                } else {
                    const rds = document.getElementsByName(`q_${i}`);
                    let jawabanAbjadSiswa = "";
                    rds.forEach(r => { if(r.checked) jawabanAbjadSiswa = r.value.trim().toUpperCase(); });

                    let kunciAbjadAsli = "A";
                    if (s.kunci_asli) {
                        kunciAbjadAsli = s.kunci_asli.trim().toUpperCase();
                    } else if (s.opsi) {
                        s.opsi.forEach((o, oIdx) => {
                            if (o.is_correct === true || o.is_correct === 1 || o.is_correct === "1" || o.is_correct === "true") {
                                kunciAbjadAsli = String.fromCharCode(65 + oIdx);
                            }
                        });
                    }

                    let isBenar = false;
                    if(jawabanAbjadSiswa !== "" && jawabanAbjadSiswa === kunciAbjadAsli) {
                        totalBobotDidapat += bobotSoal;
                        isBenar = true;
                    }

                    let jawabanTeksSiswa = "";
                    if(jawabanAbjadSiswa) {
                        const targetIdx = jawabanAbjadSiswa.charCodeAt(0) - 65;
                        if(s.opsi && s.opsi[targetIdx]) {
                            jawabanTeksSiswa = s.opsi[targetIdx].teks || "";
                        }
                    }

                    detailHasilArray.push({
                        id: s.id || (i + 1),
                        tipe: "pg",
                        jawaban_siswa: jawabanAbjadSiswa,
                        jawaban_teks: jawabanTeksSiswa.replace(/\*/g, ''),
                        kunci_asli: kunciAbjadAsli,
                        hasil: isBenar ? "BENAR" : "SALAH",
                        bobot: bobotSoal
                    });
                }
            });

            let skorAkhir = totalBobotDidapat;

            const fd = new FormData();
            fd.append('nama', `[${noUjian}] ${nm}`);
            fd.append('kelas', document.getElementById('sKelas').value);
            fd.append('mapel', mapelTerpilih);
            fd.append('guru', guruPemilik);
            fd.append('skor', skorAkhir); 
            fd.append('pelanggaran', jumlahPelanggaran);
            fd.append('detail_hasil', JSON.stringify(detailHasilArray)); 
            fd.append('log_pelanggaran', JSON.stringify(riwayatPelanggaran));

            try {
                const res = await fetch(`${API_URL}?action=simpan_hasil`, { method: 'POST', body: fd });
                const result = await res.json();

                if(result.status === "success") {
                    isExamStarted = false;
                    bersihkanJawabanLokal(noUjian);
                    alert("🎉 Jawaban Anda berhasil dikirim ke server. Ujian selesai!");
                    setTimeout(() => { location.reload(); }, 500);
                } else {
                    alert("Gagal mengirim hasil: " + result.message);
                    isSubmitting = false;
                    btn.disabled = false;
                    btn.innerText = "Selesai & Kirim Jawaban";
                    mulaiHitungMundurUjian();
                }
            } catch (err) {
                alert("Kesalahan jaringan! Gagal terhubung ke server.");
                isSubmitting = false;
                btn.disabled = false;
                btn.innerText = "Selesai & Kirim Jawaban";
            }
        }
    </script>
</body>
</html>



MONITOR 


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Monitor Real-time - Smart Imtihan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;600;700;800&display=swap" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; }
        .glass-card { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); }
        .custom-scroll::-webkit-scrollbar { width: 6px; }
        .custom-scroll::-webkit-scrollbar-thumb { background: #6366f1; border-radius: 10px; }
        .no-select { user-select: none; }
    </style>
</head>
<body class="min-h-screen p-4 md:p-8 no-select">

    <div class="max-w-7xl mx-auto">
        <!-- HEADER MONITOR -->
        <header class="flex flex-col md:flex-row justify-between items-center mb-8 glass-card p-6 rounded-[2rem] gap-4 border-b-2 border-indigo-500">
            <div class="flex items-center gap-4">
                <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-12 h-12">
                <div>
                    <h1 class="text-xl font-bold text-white uppercase tracking-tight">Monitor Ujian Real-time</h1>
                    <p id="guruDisplay" class="text-xs text-indigo-400 font-bold uppercase">Memuat data guru...</p>
                </div>
            </div>
            <div class="flex gap-3">
                <button onclick="exportExcel()" class="bg-emerald-600 hover:bg-emerald-500 text-white px-5 py-2.5 rounded-xl text-[11px] font-bold uppercase transition-all">📊 Export Excel</button>
                <button onclick="refreshData()" class="bg-slate-800 hover:bg-slate-700 text-white px-5 py-2.5 rounded-xl text-[11px] font-bold uppercase transition-all">🔄 Segarkan</button>
                <button onclick="window.close();" class="bg-red-500/10 text-red-500 px-5 py-2.5 rounded-xl text-[11px] font-bold border border-red-500/20 uppercase transition-all">Tutup</button>
            </div>
        </header>

        <!-- KARTU UTAMA MONITORING -->
        <div class="glass-card p-8 rounded-[2.5rem] min-h-[500px]">
            <div class="overflow-x-auto">
                <table class="w-full">
                    <thead>
                        <tr class="text-slate-500 text-[10px] font-bold uppercase border-b border-slate-800">
                            <th class="pb-4 text-left">Nama Peserta (Klik untuk detail)</th>
                            <th class="pb-4 text-center">Skor PG</th>
                            <th class="pb-4 text-center">Nilai Essay</th>
                            <th class="pb-4 text-center">Total Akhir</th>
                            <th class="pb-4 text-center">Status / Pelanggaran</th>
                            <th class="pb-4 text-right">Aksi</th>
                        </tr>
                    </thead>
                    <tbody id="monitorBody" class="divide-y divide-slate-800/50">
                        <tr>
                            <td colspan="6" class="text-center py-20 text-sm text-slate-500">Menghubungkan ke server atau nama guru tidak valid...</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <!-- MODAL POPUP: DETAIL JAWABAN SISWA & INPUT NILAI MANUAL ESSAY -->
    <div id="modalJawaban" class="hidden fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
        <div class="glass-card w-full max-w-4xl h-[85vh] rounded-[2.5rem] flex flex-col overflow-hidden border border-indigo-500/30 shadow-2xl">
            <div class="p-6 bg-slate-900 border-b border-slate-800 flex justify-between items-center">
                <div>
                    <h3 id="modalTitleSiswa" class="text-xl font-extrabold text-white uppercase">Detail Jawaban Siswa</h3>
                    <p id="modalSubTitle" class="text-xs text-slate-400"></p>
                </div>
                <button onclick="tutupModal()" class="bg-slate-800 hover:bg-red-600 text-white font-bold px-4 py-2 rounded-xl transition-all">Tutup [×]</button>
            </div>
            <div id="modalContentBody" class="p-6 overflow-y-auto flex-1 custom-scroll space-y-4 bg-slate-950/40"></div>
        </div>
    </div>

    <script>
        const API_URL = "api.php";
        const urlParams = new URLSearchParams(window.location.search);
        const currentGuru = urlParams.get('guru') || "";
        let rawResults = [];
        let indexSiswaTerbuka = null; 

        if (!currentGuru) {
            alert("Parameter Guru Tidak Ditemukan! Mengalihkan kembali...");
            window.location.href = "guru.php";
        } else {
            document.getElementById('guruDisplay').innerText = "GURU: " + decodeURIComponent(currentGuru);
            setInterval(refreshData, 5000);
            refreshData();
        }

        async function refreshData() {
            try {
                const res = await fetch(`${API_URL}?action=get_hasil`);
                const dataHasil = await res.json();
                
                rawResults = dataHasil.map(h => {
                    let parsedDetail = [];
                    let parsedLog = [];
                    
                    try {
                        parsedDetail = typeof h.detail_hasil === 'string' ? JSON.parse(h.detail_hasil || "[]") : (h.detail_hasil || []);
                    } catch(e) { console.error("Gagal urai detail_hasil", e); }
                    
                    try {
                        parsedLog = typeof h.log_pelanggaran === 'string' ? JSON.parse(h.log_pelanggaran || "[]") : (h.log_pelanggaran || []);
                    } catch(e) { console.error("Gagal urai log_pelanggaran", e); }

                    return { ...h, detail_hasil: parsedDetail, log_pelanggaran: parsedLog };
                });
                
                // SINKRONISASI FILTER
                const filtered = rawResults.filter(h => String(h.guru_pemilik).trim() === String(currentGuru).trim());
                const container = document.getElementById('monitorBody');
                
                if(filtered.length === 0) {
                    container.innerHTML = `<tr><td colspan="6" class="text-center py-20 text-xs text-slate-500 uppercase">Belum ada siswa yang mengikuti ujian Anda.</td></tr>`;
                    return;
                }

                container.innerHTML = filtered.map((h, index) => {
                    const jmlPelanggaran = parseInt(h.pelanggaran) || 0;
                    const skorPG = parseInt(h.skor) || 0;
                    const nilaiEssay = parseInt(h.nilai_essay) || 0;
                    const totalAkhir = skorPG + nilaiEssay;
                    
                    let statusBadge = `<span class="px-2 py-1 bg-emerald-500/10 text-emerald-400 text-[10px] rounded border border-emerald-500/20 font-bold uppercase">Aman</span>`;
                    let tombolBukaBlokir = "";

                    if (jmlPelanggaran >= 3) {
                        statusBadge = `<span class="px-2 py-1 bg-red-600 text-white text-[10px] rounded font-bold uppercase animate-pulse">🔒 Terblokir</span>`;
                        tombolBukaBlokir = `<button onclick="bukaBlokirSiswa('${h.id}')" class="bg-amber-500 hover:bg-amber-400 text-slate-950 font-bold text-[9px] px-2 py-1 rounded-md shadow ml-2 uppercase transition-all">🔓 Lepas Blokir</button>`;
                    } else if (jmlPelanggaran > 0) {
                        statusBadge = `<span class="px-2 py-1 bg-amber-500/10 text-amber-500 text-[10px] rounded border border-amber-500/20">${jmlPelanggaran}x Pelanggaran</span>`;
                    }

                    return `
                    <tr class="hover:bg-white/5 text-sm border-b border-slate-800/50 transition-all">
                        <td class="py-4">
                            <button onclick="lihatDetailSiswa(${index})" class="font-bold text-indigo-400 uppercase text-left hover:underline hover:text-indigo-300 transition-all block">
                                ${h.nama}
                            </button>
                            <div class="text-[9px] text-slate-500 font-semibold tracking-wider">${h.kelas} | ${h.mapel}</div>
                        </td>
                        <td class="text-center font-semibold text-slate-300">${skorPG}</td>
                        <td class="text-center font-semibold text-amber-400">${nilaiEssay}</td>
                        <td class="text-center font-bold text-emerald-400 text-base">${totalAkhir}</td>
                        <td class="text-center">
                            <div class="flex items-center justify-center gap-1">${statusBadge} ${tombolBukaBlokir}</div>
                        </td>
                        <td class="text-right">
                            <button onclick="hapusSiswa('${h.id}')" class="text-red-500 text-[10px] font-bold hover:underline uppercase tracking-wide">Hapus</button>
                        </td>
                    </tr>`;
                }).join('');

                if (indexSiswaTerbuka !== null) {
                    renderIsiModal(indexSiswaTerbuka);
                }

            } catch (e) { console.error("Gagal sinkronisasi data real-time.", e); }
        }

        function lihatDetailSiswa(index) {
            indexSiswaTerbuka = index;
            renderIsiModal(index);
            document.getElementById('modalJawaban').classList.remove('hidden');
        }

        function renderIsiModal(index) {
            const filtered = rawResults.filter(r => String(r.guru_pemilik).trim() === String(currentGuru).trim());
            const h = filtered[index];
            if (!h) return;

            const skorPG = parseInt(h.skor) || 0;
            const nilaiEssay = parseInt(h.nilai_essay) || 0;

            document.getElementById('modalTitleSiswa').innerText = h.nama.toUpperCase();
            document.getElementById('modalSubTitle').innerText = `${h.kelas} — ${h.mapel} (Total Skor: ${skorPG + nilaiEssay})`;

            const detailArr = Array.isArray(h.detail_hasil) ? h.detail_hasil : [];
            const containerModal = document.getElementById('modalContentBody');
            
            if (detailArr.length === 0) {
                containerModal.innerHTML = `
                    <div class="text-center py-16 space-y-2">
                        <p class="text-base text-slate-400 font-bold uppercase">Lembar Jawaban Kosong</p>
                        <p class="text-xs text-slate-600">Siswa belum menekan kirim atau struktur penyimpanan terputus.</p>
                    </div>`;
            } else {
                containerModal.innerHTML = detailArr.map((d, i) => {
                    const teksSoal = d.soal || d.pertanyaan || d.tanya || "Teks pertanyaan tidak terekam.";
                    const jwbSiswa = d.jawaban_siswa || d.jawaban || d.user_ans || "-";
                    const kunciAsli = d.kunci_asli || d.kunci || d.correct_ans || "-";
                    const jenisSoal = (d.tipe || d.jenis || "PG").toUpperCase();

                    if (jenisSoal === "ESSAY") {
                        // Cek apakah ada draft pengetikan yang belum disimpan di memori browser
                        const draftNilai = localStorage.getItem(`draft_essay_${h.id}_${i}`);
                        const skorSaatIni = (draftNilai !== null) ? draftNilai : ((d.skor_manual !== undefined) ? d.skor_manual : ((d.skor_essay !== undefined) ? d.skor_essay : ""));

                        return `
                        <div class="p-5 bg-slate-900/80 rounded-2xl border border-amber-500/30 space-y-3 text-left">
                            <div class="flex justify-between items-center text-xs border-b border-slate-800/60 pb-2">
                                <span class="font-extrabold text-amber-400 tracking-wider uppercase">Butir Pertanyaan #${i+1} [ESSAY]</span>
                                <span class="px-2 py-0.5 rounded font-black text-[10px] uppercase bg-amber-500/10 text-amber-400 border border-amber-500/20">
                                    📝 Penilaian Manual
                                </span>
                            </div>
                            <div class="text-sm text-slate-200 font-medium leading-relaxed">${teksSoal}</div>
                            
                            <div class="p-3 bg-slate-950 rounded-xl border border-slate-800 text-xs">
                                <span class="block text-[9px] font-bold text-slate-500 uppercase tracking-wider mb-1">Jawaban Tulis Siswa:</span>
                                <p class="text-white text-sm font-medium whitespace-pre-line italic">"${jwbSiswa}"</p>
                            </div>

                            <!-- Form Input Nilai dari Guru (Range 1-100) -->
                            <div class="flex flex-wrap items-center gap-3 pt-2 border-t border-slate-800/60 text-xs">
                                <label class="font-bold text-slate-400 uppercase tracking-wide">Berikan Nilai No Ini (1 - 100):</label>
                                <input type="number" id="input_nilai_${h.id}_${i}" min="1" max="100" value="${skorSaatIni}" placeholder="..." 
                                    oninput="simpanDraftEssay('${h.id}', ${i}, this.value)"
                                    class="w-20 p-2 bg-slate-950 border border-slate-700 rounded-lg text-center font-bold text-amber-400 outline-none focus:border-amber-500">
                                <button onclick="simpanNilaiEssaySatuNomor('${h.id}', ${i})" 
                                    class="bg-gradient-to-r from-amber-600 to-orange-600 hover:from-amber-500 hover:to-orange-500 text-slate-950 font-black px-4 py-2 rounded-lg uppercase tracking-wider text-[10px] transition-all active:scale-95">
                                    💾 Simpan Skor
                                </button>
                            </div>
                        </div>`;
                    } else {
                        const isCorrect = (d.hasil === 'BENAR' || d.status === 'benar' || d.is_correct == true || String(jwbSiswa).trim() === String(kunciAsli).trim());

                        return `
                        <div class="p-5 bg-slate-900/80 rounded-2xl border border-slate-800 space-y-3 text-left">
                            <div class="flex justify-between items-center text-xs border-b border-slate-800/60 pb-2">
                                <span class="font-extrabold text-indigo-400 tracking-wider uppercase">Butir Pertanyaan #${i+1} [PG]</span>
                                <span class="px-2 py-0.5 rounded font-black text-[10px] uppercase ${isCorrect ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' : 'bg-red-500/10 text-red-400 border border-red-500/20'}">
                                    ${isCorrect ? '🟢 Benar' : '🔴 Salah'}
                                </span>
                            </div>
                            <div class="text-sm text-slate-200 font-medium leading-relaxed">${teksSoal}</div>
                            <div class="grid grid-cols-1 md:grid-cols-2 gap-3 pt-2 text-xs">
                                <div class="p-3 bg-slate-950 rounded-xl border border-slate-800">
                                    <span class="block text-[9px] font-bold text-slate-500 uppercase tracking-wider mb-1">Jawaban Dipilih Siswa:</span>
                                    <span class="font-bold text-sm ${isCorrect ? 'text-emerald-400' : 'text-red-400'}">${jwbSiswa}</span>
                                </div>
                                <div class="p-3 bg-slate-950 rounded-xl border border-slate-800">
                                    <span class="block text-[9px] font-bold text-slate-500 uppercase tracking-wider mb-1">Kunci Jawaban Benar:</span>
                                    <span class="font-bold text-sm text-indigo-400">${kunciAsli}</span>
                                </div>
                            </div>
                        </div>`;
                    }
                }).join('');
            }
        }

        function tutupModal() { 
            document.getElementById('modalJawaban').classList.add('hidden'); 
            indexSiswaTerbuka = null;
        }

        // FUNGSI BARU: Mengunci angka ketikan di memori browser secara instan (Anti-Refresh-Hilang)
        function simpanDraftEssay(hasilId, indexSoal, nilai) {
            const inputElemen = document.getElementById(`input_nilai_${hasilId}_${indexSoal}`);
            
            // Batasi visual ketikan agar otomatis mentok di angka 100 jika melebihi batas
            if (parseInt(nilai) > 100) {
                inputElemen.value = 100;
                nilai = "100";
            }
            if (parseInt(nilai) < 1) {
                if(nilai !== "") {
                    inputElemen.value = 1;
                    nilai = "1";
                }
            }
            localStorage.setItem(`draft_essay_${hasilId}_${indexSoal}`, nilai);
        }

        async function simpanNilaiEssaySatuNomor(hasilId, indexSoal) {
            const inputNilai = document.getElementById(`input_nilai_${hasilId}_${indexSoal}`);
            if (!inputNilai) return alert("Elemen input nilai tidak ditemukan di sistem!");
            
            const nilaiVal = parseInt(inputNilai.value);

            // Validasi batas angka wajib di antara rentang 1 - 100
            if (isNaN(nilaiVal) || nilaiVal < 1 || nilaiVal > 100) {
                return alert("Mohon masukkan skor angka yang valid antara 1 sampai 100!");
            }

            const fd = new FormData();
            fd.append('action', 'update_nilai_essay');
            fd.append('id', hasilId);
            fd.append('index_soal', indexSoal);
            fd.append('nilai_skor', nilaiVal);

            try {
                const res = await fetch(API_URL, { method: "POST", body: fd }).then(r => r.json());
                if (res.status === 'success') {
                    alert("Nilai essay butir soal ini berhasil disimpan!");
                    
                    // Bersihkan memori draft browser karena data sudah aman masuk ke database
                    localStorage.removeItem(`draft_essay_${hasilId}_${indexSoal}`);
                    
                    refreshData(); 
                } else {
                    alert("Gagal menyimpan nilai: " + res.message);
                }
            } catch (e) {
                alert("Terjadi kesalahan sistem saat menghubungi server api.php");
            }
        }

        async function bukaBlokirSiswa(id) {
            if (confirm("Buka blokir ujian siswa ini dan bersihkan log pelanggaran?")) {
                const fd = new FormData();
                fd.append('id', id);
                fd.append('action', 'buka_blokir'); 
                try {
                    const r = await fetch(API_URL, { method: "POST", body: fd }).then(res => res.json());
                    if(r.status === 'success') { alert("Blokir berhasil dilepas!"); refreshData(); }
                } catch (e) { alert("Gagal memproses ke backend."); }
            }
        }

        async function hapusSiswa(id) { 
            if (confirm("Hapus permanen data hasil ujian siswa ini?")) { 
                const fd = new FormData();
                fd.append('id', id);
                fd.append('action', 'hapus_siswa');
                try {
                    const res = await fetch(API_URL, { method: "POST", body: fd }).then(r => r.json());
                    if (res.status === 'success') { alert("Data berhasil dihapus."); refreshData(); }
                } catch (e) { alert("Gagal menghubungi server."); }
            } 
        }

        function exportExcel() { 
            const filteredData = rawResults.filter(h => String(h.guru_pemilik).trim() === String(currentGuru).trim());
            
            if (filteredData.length === 0) {
                return alert("Tidak ada data siswa untuk di-export.");
            }

            const workbook = XLSX.utils.book_new();
            const dataPerKelas = {};
            
            filteredData.forEach(h => {
                const namaKelas = (h.kelas || "TANPA KELAS").trim().toUpperCase();
                if (!dataPerKelas[namaKelas]) {
                    dataPerKelas[namaKelas] = [];
                }
                dataPerKelas[namaKelas].push(h);
            });

            Object.keys(dataPerKelas).sort().forEach(namaKelas => {
                const siswaDiKelasIni = dataPerKelas[namaKelas];

                const excelRows = siswaDiKelasIni.map((h, idx) => {
                    const skorPG = parseInt(h.skor) || 0;
                    const nilaiEssay = parseInt(h.nilai_essay) || 0;
                    const jmlPelanggaran = parseInt(h.pelanggaran) || 0;
                    
                    let status = "Aman";
                    if (jmlPelanggaran >= 3) status = "Terblokir";
                    else if (jmlPelanggaran > 0) status = `${jmlPelanggaran}x Pelanggaran`;

                    return {
                        "No": idx + 1,
                        "Nama Siswa": h.nama ? h.nama.toUpperCase() : "-",
                        "Kelas": h.kelas || "-",
                        "Mata Pelajaran": h.mapel || "-",
                        "Skor PG": skorPG,
                        "Nilai Essay": nilaiEssay,
                        "Total Akhir": skorPG + nilaiEssay,
                        "Jumlah Pelanggaran": jmlPelanggaran,
                        "Status": status
                    };
                });

                const worksheet = XLSX.utils.json_to_sheet(excelRows);

                worksheet['!cols'] = [
                    {wch: 5},   // No
                    {wch: 30},  // Nama Siswa
                    {wch: 12},  // Kelas
                    {wch: 25},  // Mata Pelajaran
                    {wch: 10},  // Skor PG
                    {wch: 12},  // Nilai Essay
                    {wch: 12},  // Total Akhir
                    {wch: 18},  // Jumlah Pelanggaran
                    {wch: 15}   // Status
                ];

                const namaSheetBersih = namaKelas.substring(0, 31).replace(/[\\*?:/[\]]/g, "_");
                XLSX.utils.book_append_sheet(workbook, worksheet, namaSheetBersih);
            });

            const namaFile = `Hasil_SmartImtihan_Per_Kelas_${decodeURIComponent(currentGuru).replace(/[^a-z0-9]/gi, '_')}.xlsx`;
            XLSX.writeFile(workbook, namaFile);
        }
    </script>
</body>
</html>


GURU


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard Guru - Smart Imtihan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; }
        .glass-card { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); }
        .input-dark { background: #1e293b; border: 1px solid #334155; color: white; transition: all 0.3s; }
        .input-dark:focus { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); }
    </style>
</head>
<body class="min-h-screen">

    <!-- TAMPILAN LOGIN -->
    <div id="authArea" class="flex items-center justify-center min-h-screen p-4">
        <div class="glass-card p-10 rounded-[2.5rem] w-full max-w-md text-center border-t-4 border-indigo-500">
            <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-24 h-24 mx-auto mb-6">
            <h3 class="text-3xl font-extrabold text-white uppercase">LOGIN Smart Imtihan Guru MAN 3 Kuningan</h3>
            <p class="text-slate-400 text-[10px] tracking-widest mb-10 mt-2 uppercase font-bold">Created By Mamat Rahmat, Lc</p>
            <div class="space-y-4 mt-8">
                <input type="text" id="user" placeholder="Nama Guru" class="w-full p-4 input-dark rounded-2xl outline-none">
                <input type="password" id="pass" placeholder="Password" class="w-full p-4 input-dark rounded-2xl outline-none">
                <button onclick="login()" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 text-white rounded-2xl font-bold uppercase transition-all">Masuk</button>
            </div>
        </div>
    </div>

    <!-- MAIN DASHBOARD -->
    <div id="mainDashboard" class="hidden p-4 md:p-8 max-w-4xl mx-auto">
        <header class="flex flex-col md:flex-row justify-between items-center mb-10 glass-card p-6 rounded-[2rem] gap-4 border-b-2 border-indigo-500">
            <div class="flex items-center gap-4">
                <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-12 h-12">
                <div>
                    <h1 id="welcomeText" class="text-xl font-bold text-white uppercase">Dashboard</h1>
                    <span class="text-[10px] text-indigo-400 font-bold uppercase">Pusat Kendali Ujian</span>
                </div>
            </div>
            <div class="flex gap-2">
                <button onclick="bukaMonitor()" class="bg-indigo-600 hover:bg-indigo-500 text-white px-5 py-2.5 rounded-xl text-[11px] font-bold uppercase transition-all">👁️ Monitor Real-time</button>
                <button onclick="bukaEditorBaru()" class="bg-emerald-600 hover:bg-emerald-500 text-white px-5 py-2.5 rounded-xl text-[11px] font-bold uppercase transition-all">➕ Buat Ujian Baru</button>
                <!-- Tambahkan tombol ini di barisan menu atas guru.php -->
<a href="analisis.php" class="inline-flex items-center gap-2 px-5 py-3 bg-gradient-to-r from-indigo-600 to-blue-600 hover:from-indigo-500 hover:to-blue-500 text-white rounded-xl text-xs font-black uppercase tracking-wider shadow-lg transition-all active:scale-95">
    📊 Analisis Butir Soal (Grafik)
</a>
                <button onclick="location.reload()" class="bg-slate-800 text-red-400 px-4 py-2.5 rounded-xl text-[11px] font-bold uppercase">Keluar</button>
            </div>
        </header>

        <!-- DAFTAR BANK SOAL -->
        <div class="glass-card p-8 rounded-[2.5rem]">
            <h2 class="font-bold text-slate-400 text-xs uppercase mb-6 tracking-wider">📚 Daftar Bank Soal Anda</h2>
            <div id="listMapelAktif" class="space-y-4"></div>
        </div>
    </div>

    <script>
        const API_URL = "api.php";
        let currentGuru = "";

        function login() {
            const u = document.getElementById('user').value.trim();
            const p = document.getElementById('pass').value.trim();
            if(u && p) {
                currentGuru = u;
                document.getElementById('authArea').classList.add('hidden');
                document.getElementById('mainDashboard').classList.remove('hidden');
                document.getElementById('welcomeText').innerText = "GURU: " + u;
                loadBankSoal();
            } else { alert("Isi data login!"); }
        }

        function bukaMonitor() {
            window.open(`monitor.php?guru=${encodeURIComponent(currentGuru)}`, '_blank');
        }

        function bukaEditorBaru() {
            window.location.href = `edit.php?guru=${encodeURIComponent(currentGuru)}`;
        }

        async function loadBankSoal() {
            try {
                const res = await fetch(`${API_URL}?action=get_mapel`).then(r => r.json());
                const filtered = res.filter(m => m.guru === currentGuru);
                const container = document.getElementById('listMapelAktif');
                
                if(filtered.length === 0) {
                    container.innerHTML = `<p class="text-xs text-slate-500 uppercase text-center py-8">Belum ada ujian dibuat.</p>`;
                    return;
                }

                container.innerHTML = filtered.map((m) => `
                    <div class="bg-slate-900 p-5 rounded-2xl border border-slate-800 flex justify-between items-center hover:border-slate-700 transition-all">
                        <div>
                            <span class="text-sm font-bold text-white block uppercase tracking-tight">${m.mapel}</span>
                            <span class="text-[10px] text-slate-500 font-semibold block mt-1">🕒 Mulai: ${m.waktu || '-'} | ⏳ Durasi: ${m.durasi || 60} Menit</span>
                        </div>
                        <div class="flex gap-3">
                            <button onclick="window.location.href='edit.php?guru=${encodeURIComponent(currentGuru)}&mapel=${encodeURIComponent(m.mapel)}'" class="bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 px-4 py-2 rounded-xl text-xs font-bold uppercase hover:bg-indigo-600 hover:text-white transition-all">Kelola & Edit</button>
                            <button onclick="hapusMapel('${m.mapel}')" class="text-red-500 hover:underline text-xs font-bold uppercase px-2">Hapus</button>
                        </div>
                    </div>`).join('');
            } catch (e) { console.error(e); }
        }

        async function hapusMapel(mapelName) {
            if(confirm(`Hapus seluruh bank soal untuk mata pelajaran: ${mapelName}?`)) {
                const fd = new FormData();
                fd.append('action', 'hapus_mapel');
                fd.append('mapel', mapelName);
                fd.append('guru', currentGuru);
                await fetch(API_URL, { method: "POST", body: fd });
                loadBankSoal();
            }
        }
    </script>
</body>
</html>


EDIT


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Editor Soal Ujian - Smart Imtihan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; }
        .glass-card { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); }
        .input-dark { background: #1e293b; border: 1px solid #334155; color: white; transition: all 0.3s; }
        .input-dark:focus { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); }
        .custom-scroll::-webkit-scrollbar { width: 6px; }
        .custom-scroll::-webkit-scrollbar-thumb { background: #6366f1; border-radius: 10px; }
    </style>
</head>
<body class="min-h-screen p-4 md:p-8">

    <div class="max-w-5xl mx-auto space-y-6">
        
        <!-- HEADER EDITOR -->
        <header class="flex flex-col md:flex-row justify-between items-center glass-card p-6 rounded-[2rem] gap-4 border-b-2 border-indigo-500">
            <div>
                <h1 id="pageTitle" class="text-xl font-black text-white uppercase tracking-tight">Editor Ujian</h1>
                <p id="guruDisplay" class="text-[10px] text-indigo-400 font-bold uppercase"></p>
            </div>
            <div class="flex gap-2">
                <button onclick="simpanSeluruhUjian()" class="bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase transition-all shadow-lg shadow-indigo-500/20">💾 Simpan Semua Perubahan</button>
                <button onclick="kembaliKeDashboard()" class="bg-slate-800 hover:bg-slate-700 text-white px-5 py-3 rounded-xl text-xs font-bold uppercase transition-all">Kembali</button>
            </div>
        </header>

        <!-- 📦 COMPONENT IMPORT SOAL VIA .TXT -->
        <div class="glass-card p-6 rounded-[2rem] border border-emerald-500/30 bg-gradient-to-r from-slate-900 via-slate-900/40 to-slate-900">
            <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-3">
                <div>
                    <h2 class="text-xs font-black text-emerald-400 uppercase tracking-wider flex items-center gap-1.5">📦 Import Massal Soal Standard (.txt)</h2>
                    <p class="text-[11px] text-slate-400 mt-1">Pisahkan antar soal menggunakan **satu atau beberapa kali Enter (Baris Kosong)**:</p>
                    <div class="bg-slate-950 p-3 rounded text-slate-400 font-mono text-[10px] space-y-1 my-1 border border-slate-800 leading-relaxed">
                        1. Soal nomor satu...<br>
                        A. Pilihan A<br>
                        B. Pilihan B* <span class="text-slate-600">(Kunci)</span><br>
                        C. Pilihan C<br><br>
                        <span class="text-emerald-400 font-bold">&lt;-- Berikan satu baris kosong (Enter) disini --&gt;</span><br><br>
                        2. Soal nomor dua...<br>
                        A. Pilihan A<br>
                        B. Pilihan B
                    </div>
                </div>
                <div class="w-full md:w-auto flex flex-col sm:flex-row gap-2 items-stretch sm:items-center">
                    <input type="file" id="fileTxtSoal" accept=".txt" class="text-[10px] text-slate-400 file:mr-3 file:py-2 file:px-4 file:rounded-xl file:border-0 file:text-[11px] file:font-black file:bg-emerald-600 file:text-white hover:file:bg-emerald-500 cursor-pointer bg-slate-950 border border-slate-800 rounded-xl p-1.5 min-w-[240px]">
                    <button type="button" onclick="prosesUploadTxtSoal()" class="py-2.5 px-5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-black uppercase transition-all whitespace-nowrap shadow-md">
                        ⚙️ Proses File
                    </button>
                </div>
            </div>
        </div>

        <!-- LAYOUT UTAMA -->
        <div class="grid grid-cols-1 md:grid-cols-12 gap-6 items-start">
            <!-- PANEL SETTING WAKTU & INFORMASI (KIRI) -->
            <div class="md:col-span-4 space-y-6">
                <div class="glass-card p-6 rounded-[2rem] space-y-4">
                    <h2 class="text-xs font-bold text-indigo-400 uppercase tracking-wider mb-2">⚙️ Pengaturan Ujian</h2>
                    
                    <div>
                        <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1">Mata Pelajaran</label>
                        <input type="text" id="mapelName" placeholder="Contoh: Matematika" class="w-full p-3 text-sm input-dark rounded-xl font-bold">
                    </div>

                    <div>
                        <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1">Tanggal & Waktu Mulai</label>
                        <input type="datetime-local" id="tglMulai" class="w-full p-3 text-xs input-dark rounded-xl">
                    </div>

                    <div>
                        <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1">Durasi Pengerjaan (Menit)</label>
                        <input type="number" id="durasiMenit" value="60" class="w-full p-3 text-sm input-dark rounded-xl font-semibold">
                    </div>

                    <div class="p-3 bg-slate-900/50 rounded-xl border border-slate-800 flex justify-between items-center">
                        <div>
                            <span class="text-xs font-bold text-white block uppercase">Sistem Anti-Cheat</span>
                            <span class="text-[9px] text-slate-500 block">Kunci tab siswa</span>
                        </div>
                        <label class="relative inline-flex items-center cursor-pointer">
                            <input type="checkbox" id="antiCheatToggle" checked class="sr-only peer">
                            <div class="w-9 h-5 bg-slate-700 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div>
                        </label>
                    </div>
                </div>

                <div class="glass-card p-6 rounded-[2rem] text-xs text-slate-400 space-y-2">
                    <h3 class="font-bold text-white uppercase text-[10px] text-indigo-400">💡 Petunjuk Singkat:</h3>
                    <p>1. Pilih Jenis Soal terlebih dahulu (**Pilihan Ganda** atau **Essay**).</p>
                    <p>2. Untuk Essay, Anda tidak perlu mengisi opsi jawaban. Cukup tuliskan pertanyaan utamanya.</p>
                    <p>3. Untuk Pilihan Ganda, klik tombol **A, B, C, D, atau E** lalu centang bulatan **Kunci Jawaban**.</p>
                </div>
            </div>

            <!-- PANEL EDITOR DAFTAR BUTIR SOAL (KANAN) -->
            <div class="md:col-span-8 space-y-6">
                <div class="flex justify-between items-center">
                    <h2 class="text-sm font-black text-white uppercase tracking-tight">📝 Daftar Butir Pertanyaan (<span id="totalSoalLabel">0</span>)</h2>
                    <div class="flex gap-2">
                        <button onclick="tambahButirSoalBarunya('PG')" class="bg-emerald-600 hover:bg-emerald-500 text-white px-3 py-2 rounded-xl text-[11px] font-bold uppercase transition-all">➕ PG</button>
                        <button onclick="tambahButirSoalBarunya('ESSAY')" class="bg-blue-600 hover:bg-blue-500 text-white px-3 py-2 rounded-xl text-[11px] font-bold uppercase transition-all">➕ Essay</button>
                    </div>
                </div>

                <div id="containerButirSoal" class="space-y-6"></div>

                <div class="grid grid-cols-2 gap-3 pt-2">
                    <button onclick="tambahButirSoalBarunya('PG')" class="bg-slate-900 hover:bg-slate-800 border border-slate-800 text-emerald-400 p-4 rounded-2xl text-xs font-bold uppercase transition-all">+ Tambah Pilihan Ganda (PG)</button>
                    <button onclick="tambahButirSoalBarunya('ESSAY')" class="bg-slate-900 hover:bg-slate-800 border border-slate-800 text-blue-400 p-4 rounded-2xl text-xs font-bold uppercase transition-all">+ Tambah Soal Essay</button>
                </div>
            </div>
        </div>
    </div>

    <script>
        const API_URL = "api.php";
        const urlParams = new URLSearchParams(window.location.search);
        const currentGuru = urlParams.get('guru') || "";
        const targetMapel = urlParams.get('mapel') || "";
        
        let soalSiap = [];
        let isEditMode = false;
        let activeTabOpsi = []; 

        if (!currentGuru) {
            alert("Sesi guru tidak valid.");
            window.location.href = "guru.php";
        } else {
            document.getElementById('guruDisplay').innerText = "GURU OPERATOR: " + decodeURIComponent(currentGuru);
            initialLoad();
        }

        async function initialLoad() {
            if (targetMapel) {
                isEditMode = true;
                document.getElementById('mapelName').value = decodeURIComponent(targetMapel);
                document.getElementById('mapelName').readOnly = true;
                
                try {
                    const res = await fetch(`${API_URL}?action=get_mapel`).then(r => r.json());
                    const item = res.find(m => m.guru === currentGuru && m.mapel === decodeURIComponent(targetMapel));
                    if (item) {
                        document.getElementById('tglMulai').value = item.waktu ? item.waktu.replace(" ", "T") : "";
                        document.getElementById('durasiMenit').value = item.durasi || 60;
                        document.getElementById('antiCheatToggle').checked = (item.anti_cheat == 1);
                        
                        let soalArray = [];
                        try {
                            soalArray = typeof item.soal === 'string' ? JSON.parse(decodeURIComponent(item.soal)) : item.soal;
                        } catch(e) { soalArray = item.soal; }
                        
                        soalSiap = Array.isArray(soalArray) ? soalArray : [];
                    }
                } catch (e) { console.error(e); }
            } else {
                isEditMode = false;
                tambahButirSoalBarunya('PG');
            }
            
            activeTabOpsi = soalSiap.map(() => 0);
            renderEditor();
        }

        function renderEditor() {
            const container = document.getElementById('containerButirSoal');
            document.getElementById('totalSoalLabel').innerText = soalSiap.length;

            if (soalSiap.length === 0) {
                container.innerHTML = `<div class="glass-card p-12 text-center rounded-3xl text-xs text-slate-500 uppercase">Belum ada butir pertanyaan.</div>`;
                return;
            }

            container.innerHTML = soalSiap.map((s, sIdx) => {
                const cleanTanya = (s.tanya || '').replace(/<br>/g, '\n');
                const currentTabIdx = activeTabOpsi[sIdx] !== undefined ? activeTabOpsi[sIdx] : 0;
                
                // Set default jenis jika belum terekam di DB terdahulu
                if (!s.jenis) {
                    s.jenis = (s.opsi && s.opsi.length > 0 && s.opsi[0].teks !== "") ? "PG" : "ESSAY";
                }

                const isEssay = (s.jenis === 'ESSAY');

                // Generate kerangka opsi jika bertipe PG
                if (!isEssay && (!s.opsi || s.opsi.length < 5)) {
                    s.opsi = Array.from({length: 5}, (_, idx) => ({
                        teks: s.opsi && s.opsi[idx] ? s.opsi[idx].teks : ``,
                        gambar: s.opsi && s.opsi[idx] ? s.opsi[idx].gambar : null,
                        is_correct: s.opsi && s.opsi[idx] ? s.opsi[idx].is_correct : (idx === 0)
                    }));
                }

                const opsiTerpilih = !isEssay ? s.opsi[currentTabIdx] : null;
                const hurufKunciSesuai = (s.kunci_asli || "A").trim().toUpperCase();

                return `
                <div class="glass-card p-6 rounded-3xl border ${isEssay ? 'border-blue-500/30' : 'border-slate-800'} space-y-4 text-left">
                    <div class="flex flex-wrap justify-between items-center bg-slate-950 p-3 rounded-xl border border-slate-800 gap-2">
                        <div class="flex items-center gap-3">
                            <span class="text-xs font-black ${isEssay ? 'text-blue-400' : 'text-indigo-400'} uppercase">Butir Soal #${sIdx+1}</span>
                            <!-- DROPDOWN SINKRONISASI JENIS SOAL -->
                            <select onchange="ubahJenisSoal(${sIdx}, this.value)" class="bg-slate-900 border border-slate-700 rounded-lg text-[11px] font-bold text-white px-2 py-1 outline-none">
                                <option value="PG" ${s.jenis === 'PG' ? 'selected' : ''}>📋 PILIHAN GANDA</option>
                                <option value="減SAY" ${isEssay ? 'selected' : ''} value="ESSAY">📝 ESSAY / ISIAN</option>
                            </select>
                        </div>
                        <div class="flex items-center gap-4">
                            <div class="flex items-center gap-1">
                                <span class="text-[10px] text-slate-500 font-bold">BOBOT:</span>
                                <input type="number" min="0" value="${s.bobot !== undefined ? s.bobot : (isEssay ? 0 : 1)}" onchange="updateDataSoal(${sIdx}, 'bobot', this.value)" class="w-12 bg-slate-900 text-center text-xs font-bold rounded-lg border border-slate-700 text-white py-1">
                            </div>
                            <button onclick="hapusButirSoal(${sIdx})" class="text-xs text-red-400 hover:text-red-500 font-bold uppercase">❌ Hapus</button>
                        </div>
                    </div>

                    ${s.gambar ? `
                    <div class="relative w-fit p-1 bg-slate-950 rounded-xl border border-slate-800">
                        <img src="${s.gambar}" class="max-h-36 rounded-lg">
                        <button onclick="hapusGambarMedia(${sIdx}, 'soal')" class="absolute -top-2 -right-2 bg-red-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold">×</button>
                    </div>` : ''}

                    <div>
                        <textarea onchange="updateDataSoal(${sIdx}, 'tanya', this.value)" placeholder="Tulis teks pertanyaan utama ${isEssay ? 'essay' : ''} di sini..." class="w-full p-4 bg-slate-950 border border-slate-800 rounded-xl text-sm text-white h-32 outline-none resize-y leading-relaxed">${cleanTanya}</textarea>
                        <label class="inline-flex items-center gap-1.5 text-[9px] bg-slate-800 hover:bg-slate-700 px-3 py-1.5 mt-1 rounded-lg cursor-pointer font-bold uppercase text-slate-300">
                            📷 Upload Gambar Soal <input type="file" class="hidden" accept="image/*" onchange="uploadGambarMedia(event, ${sIdx}, 'soal')">
                        </label>
                    </div>

                    <!-- BLOK PILIHAN GANDA: HANYA MUNCUL JIKA BUKAN ESSAY -->
                    ${isEssay ? `
                    <div class="p-4 bg-blue-950/20 rounded-2xl border border-blue-500/20">
                        <span class="text-[10px] text-blue-400 font-extrabold uppercase tracking-wider block">ℹ️ Mode Lembar Jawaban Essay Aktif</span>
                        <p class="text-xs text-slate-400 mt-1">Siswa akan diberikan kolom kosong di lembar ujian untuk mengetik jawaban mereka secara mandiri. Nilai dapat dimasukkan secara manual melalui menu Monitoring Hasil.</p>
                    </div>
                    ` : `
                    <div class="pt-3 border-t border-slate-800/60 space-y-3">
                        <label class="block text-[10px] text-indigo-400 font-bold uppercase tracking-wider">🗂️ Kelola Opsi Jawaban & Kunci (Kunci Saat Ini: ${hurufKunciSesuai}):</label>
                        
                        <div class="flex gap-1.5">
                            ${s.opsi.map((o, oIdx) => {
                                const isTabAktif = (currentTabIdx === oIdx);
                                const hurufKini = String.fromCharCode(65+oIdx);
                                const isKunci = (hurufKini === hurufKunciSesuai);
                                return `
                                <button onclick="gantiTabOpsi(${sIdx}, ${oIdx})" type="button" class="flex-1 py-2 text-center rounded-xl text-xs font-black transition-all ${isTabAktif ? 'bg-indigo-600 text-white ring-2 ring-indigo-400' : 'bg-slate-950 text-slate-400'} border border-slate-800 flex justify-center items-center gap-1">
                                    ${hurufKini} ${isKunci ? '⭐' : ''}
                                </button>
                                `;
                            }).join('')}
                        </div>

                        <div class="p-4 bg-slate-950/80 rounded-2xl border border-indigo-500/20 space-y-3">
                            <div class="flex justify-between items-center">
                                <span class="text-[10px] text-slate-400 font-extrabold uppercase">Mengedit Konten Opsi [ ${String.fromCharCode(65+currentTabIdx)} ] :</span>
                                <label class="flex items-center gap-1.5 text-xs text-emerald-400 font-bold cursor-pointer">
                                    <input type="radio" name="kunci_${sIdx}" ${String.fromCharCode(65+currentTabIdx) === hurufKunciSesuai ? 'checked' : ''} onchange="jadikanKunciJawaban(${sIdx}, ${currentTabIdx})" class="w-4 h-4 accent-emerald-500">
                                    Jadikan Kunci Jawaban Benar
                                </label>
                            </div>

                            ${opsiTerpilih && opsiTerpilih.gambar ? `
                            <div class="relative w-fit p-1 bg-slate-900 rounded-lg border border-slate-800">
                                <img src="${opsiTerpilih.gambar}" class="max-h-24 rounded">
                                <button onclick="hapusGambarMedia(${sIdx}, 'opsi', ${currentTabIdx})" class="absolute -top-1.5 -right-1.5 bg-red-600 text-white text-[10px] rounded-full w-4 h-4 flex items-center justify-center">×</button>
                            </div>` : ''}

                            <input type="text" value="${opsiTerpilih ? (opsiTerpilih.teks || '') : ''}" onchange="updateOpsiTeks(${sIdx}, ${currentTabIdx}, this.value)" placeholder="Tulis isi jawaban untuk Opsi ${String.fromCharCode(65+currentTabIdx)}..." class="w-full p-3 text-xs input-dark rounded-xl">
                            
                            <label class="inline-flex items-center gap-1 text-[9px] bg-slate-800 hover:bg-slate-700 px-2.5 py-1.5 rounded-md cursor-pointer font-bold uppercase text-slate-400">
                                1️⃣ Upload Gambar Opsi ${String.fromCharCode(65+currentTabIdx)}
                                <input type="file" class="hidden" accept="image/*" onchange="uploadGambarMedia(event, ${sIdx}, 'opsi', ${currentTabIdx})">
                            </label>
                        </div>
                    </div>
                    `}
                </div>`;
            }).join('');
        }

        function ubahJenisSoal(sIdx, jenisBaru) {
            soalSiap[sIdx].jenis = jenisBaru;
            if (jenisBaru === 'ESSAY') {
                soalSiap[sIdx].kunci_asli = "-";
                soalSiap[sIdx].opsi = []; // Kosongkan opsi karena bertipe Essay
                soalSiap[sIdx].bobot = 0; // Set standar bobot awal essay adalah 0 (dinilai manual nanti)
            } else {
                soalSiap[sIdx].kunci_asli = "A";
                soalSiap[sIdx].bobot = 1;
            }
            renderEditor();
        }

        function prosesUploadTxtSoal() {
            const fileInput = document.getElementById('fileTxtSoal');
            if (!fileInput.files || fileInput.files.length === 0) {
                return alert("Silakan pilih file format .txt terlebih dahulu!");
            }

            const file = fileInput.files[0];
            const reader = new FileReader();

            reader.onload = function(e) {
                const isiTeks = e.target.result;
                const kumpulanBlokRaw = isiTeks.split(/\n\s*\r?\n/);
                
                let daftarSoalBaru = [];
                const regexNomor = /^\s*(\d+)\.\s*(.*)/; 
                const regexOpsi = /^\s*([A-Ea-e])\.\s*(.*)/;

                kumpulanBlokRaw.forEach((blok) => {
                    const barisDalamBlok = blok.split(/\r?\n/).map(b => b.trim()).filter(b => b.length > 0);
                    if (barisDalamBlok.length === 0) return;

                    let teksPertanyaanKumpul = [];
                    let opsiData = Array.from({length: 5}, (_, idx) => ({
                        teks: "", gambar: null, is_correct: false
                    }));
                    let hurufKunciTerdeteksi = "A";
                    let adaOpsiValid = false;

                    barisDalamBlok.forEach((baris) => {
                        if (regexOpsi.test(baris)) {
                            const matchOpsi = baris.match(regexOpsi);
                            const hurufOpsi = matchOpsi[1].toUpperCase();
                            let teksOpsi = matchOpsi[2];
                            let isKunci = false;

                            if (teksOpsi.endsWith('*')) {
                                isKunci = true;
                                teksOpsi = teksOpsi.slice(0, -1).trim();
                                hurufKunciTerdeteksi = hurufOpsi;
                            }

                            const oIdx = hurufOpsi.charCodeAt(0) - 65; 
                            if (oIdx >= 0 && oIdx < 5) {
                                opsiData[oIdx].teks = teksOpsi;
                                opsiData[oIdx].is_correct = isKunci;
                                adaOpsiValid = true;
                            }
                        } 
                        else if (regexNomor.test(baris)) {
                            const matchNomor = baris.match(regexNomor);
                            teksPertanyaanKumpul.push(matchNomor[2]);
                        } 
                        else {
                            teksPertanyaanKumpul.push(baris);
                        }
                    });

                    if (teksPertanyaanKumpul.length > 0) {
                        // Jika dalam file txt tidak ada format opsi sama sekali, jadikan otomatis ESSAY
                        let tipeDeteksi = adaOpsiValid ? "PG" : "ESSAY";

                        if (tipeDeteksi === "PG") {
                            let cekKunci = opsiData.some(o => o.is_correct);
                            if (!cekKunci && adaOpsiValid) {
                                opsiData[0].is_correct = true;
                                hurufKunciTerdeteksi = "A";
                            }
                            opsiData.forEach((o, idx) => {
                                o.is_correct = (String.fromCharCode(65 + idx) === hurufKunciTerdeteksi);
                            });
                        } else {
                            opsiData = [];
                            hurufKunciTerdeteksi = "-";
                        }

                        daftarSoalBaru.push({
                            id: daftarSoalBaru.length + 1,
                            jenis: tipeDeteksi,
                            tanya: teksPertanyaanKumpul.join('\n'),
                            bobot: tipeDeteksi === "PG" ? 1 : 0,
                            gambar: null,
                            kunci_asli: hurufKunciTerdeteksi,
                            opsi: opsiData
                        });
                    }
                });

                if (daftarSoalBaru.length > 0) {
                    soalSiap = [...soalSiap, ...daftarSoalBaru];
                    soalSiap.forEach((s, i) => s.id = i + 1);
                    activeTabOpsi = soalSiap.map(() => 0);
                    alert(`🎉 Sukses mengimpor ${daftarSoalBaru.length} butir soal.`);
                    renderEditor();
                    fileInput.value = ""; 
                } else {
                    alert("❌ Gagal memproses file. Periksa format teks.");
                }
            };
            reader.readAsText(file);
        }

        function gantiTabOpsi(sIdx, oIdx) {
            activeTabOpsi[sIdx] = oIdx;
            renderEditor();
        }

        function updateDataSoal(idx, field, val) {
            if (field === 'bobot') soalSiap[idx].bobot = parseInt(val) || 0;
            else soalSiap[idx][field] = val;
        }

        function updateOpsiTeks(sIdx, oIdx, val) {
            soalSiap[sIdx].opsi[oIdx].teks = val;
        }

        function jadikanKunciJawaban(sIdx, oIdx) {
            const hurufTerpilih = String.fromCharCode(65 + oIdx);
            soalSiap[sIdx].kunci_asli = hurufTerpilih;
            soalSiap[sIdx].opsi.forEach((o, idx) => {
                o.is_correct = (idx === oIdx);
            });
            renderEditor();
        }

        function tambahButirSoalBarunya(jenisTerpilih = 'PG') {
            soalSiap.push({
                id: soalSiap.length + 1,
                jenis: jenisTerpilih,
                tanya: "",
                bobot: jenisTerpilih === 'PG' ? 1 : 0,
                gambar: null,
                kunci_asli: jenisTerpilih === 'PG' ? "A" : "-",
                opsi: jenisTerpilih === 'PG' ? Array.from({length: 5}, (_, idx) => ({
                    teks: "",
                    gambar: null,
                    is_correct: (idx === 0)
                })) : []
            });
            activeTabOpsi.push(0);
            renderEditor();
        }

        function hapusButirSoal(index) {
            if(confirm("Hapus butir pertanyaan nomor " + (index + 1) + "?")) {
                soalSiap.splice(index, 1);
                activeTabOpsi.splice(index, 1);
                soalSiap.forEach((s, i) => s.id = i + 1);
                renderEditor();
            }
        }

        function hapusGambarMedia(sIdx, tipe, oIdx = null) {
            if (tipe === 'soal') {
                soalSiap[sIdx].gambar = null;
            } else if (tipe === 'opsi') {
                soalSiap[sIdx].opsi[oIdx].gambar = null;
            }
            renderEditor();
        }

        async function uploadGambarMedia(event, sIdx, tipe, oIdx = null) {
            const file = event.target.files[0];
            if (!file) return;
            const fd = new FormData();
            fd.append('gambar', file);
            try {
                const res = await fetch(`${API_URL}?action=upload_gambar`, { method: "POST", body: fd }).then(r => r.json());
                if (res.status === 'success') {
                    if (tipe === 'soal') {
                        soalSiap[sIdx].gambar = res.url;
                    } else if (tipe === 'opsi') {
                        soalSiap[sIdx].opsi[oIdx].gambar = res.url;
                    }
                    renderEditor();
                } else { alert("Gagal mengunggah berkas gambar."); }
            } catch (err) { alert("Sistem upload bermasalah."); }
        }

        async function simpanSeluruhUjian() {
            const mapel = document.getElementById('mapelName').value.trim();
            const waktu = document.getElementById('tglMulai').value;
            const durasi = document.getElementById('durasiMenit').value || 60;
            const antiCheat = document.getElementById('antiCheatToggle').checked ? 1 : 0;

            if(!mapel || !waktu) return alert("Mata Pelajaran dan Waktu Mulai wajib ditentukan!");
            if(soalSiap.length === 0) return alert("Buat minimal 1 soal!");

            // Validasi & Rekonstruksi sinkronisasi data sebelum dilempar JSON ke API / DB
            soalSiap.forEach(s => {
                if (!s.jenis) s.jenis = "PG";
                
                if (s.jenis === 'PG') {
                    if(!s.kunci_asli || s.kunci_asli === "-") s.kunci_asli = "A";
                    if(!s.opsi || s.opsi.length === 0) {
                        s.opsi = Array.from({length: 5}, (_, idx) => ({
                            teks: "", gambar: null, is_correct: (idx === 0)
                        }));
                    }
                    s.opsi.forEach((o, idx) => {
                        o.is_correct = (String.fromCharCode(65 + idx) === s.kunci_asli.trim().toUpperCase());
                    });
                } else {
                    s.kunci_asli = "-";
                    s.opsi = []; // Dipastikan bersih dari array dummy agar api.php membacanya murni ESSAY
                }
            });

            const fd = new FormData();
            fd.append('action', isEditMode ? 'edit_soal' : 'publish_soal');
            fd.append('mapel', mapel);
            fd.append('guru', currentGuru);
            fd.append('waktu', waktu);
            fd.append('durasi', durasi);
            fd.append('anti_cheat', antiCheat);
            fd.append('soal', JSON.stringify(soalSiap));

            try {
                const res = await fetch(API_URL, { method: "POST", body: fd }).then(r => r.json());
                if(res.status === 'success') {
                    alert("Seluruh data ujian (Termasuk Pilihan Ganda & Essay) berhasil disimpan!");
                    kembaliKeDashboard();
                } else { alert("Gagal menyimpan ke DB: " + res.message); }
            } catch(e) { alert("Koneksi gagal."); }
        }

        function kembaliKeDashboard() {
            window.location.href = `guru.php`;
        }
    </script>
</body>
</html>


API


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Editor Soal Ujian - Smart Imtihan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; }
        .glass-card { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); }
        .input-dark { background: #1e293b; border: 1px solid #334155; color: white; transition: all 0.3s; }
        .input-dark:focus { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); }
        .custom-scroll::-webkit-scrollbar { width: 6px; }
        .custom-scroll::-webkit-scrollbar-thumb { background: #6366f1; border-radius: 10px; }
    </style>
</head>
<body class="min-h-screen p-4 md:p-8">

    <div class="max-w-5xl mx-auto space-y-6">
        
        <!-- HEADER EDITOR -->
        <header class="flex flex-col md:flex-row justify-between items-center glass-card p-6 rounded-[2rem] gap-4 border-b-2 border-indigo-500">
            <div>
                <h1 id="pageTitle" class="text-xl font-black text-white uppercase tracking-tight">Editor Ujian</h1>
                <p id="guruDisplay" class="text-[10px] text-indigo-400 font-bold uppercase"></p>
            </div>
            <div class="flex gap-2">
                <button onclick="simpanSeluruhUjian()" class="bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-3 rounded-xl text-xs font-bold uppercase transition-all shadow-lg shadow-indigo-500/20">💾 Simpan Semua Perubahan</button>
                <button onclick="kembaliKeDashboard()" class="bg-slate-800 hover:bg-slate-700 text-white px-5 py-3 rounded-xl text-xs font-bold uppercase transition-all">Kembali</button>
            </div>
        </header>

        <!-- 📦 COMPONENT IMPORT SOAL VIA .TXT -->
        <div class="glass-card p-6 rounded-[2rem] border border-emerald-500/30 bg-gradient-to-r from-slate-900 via-slate-900/40 to-slate-900">
            <div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-3">
                <div>
                    <h2 class="text-xs font-black text-emerald-400 uppercase tracking-wider flex items-center gap-1.5">📦 Import Massal Soal Standard (.txt)</h2>
                    <p class="text-[11px] text-slate-400 mt-1">Pisahkan antar soal menggunakan **satu atau beberapa kali Enter (Baris Kosong)**:</p>
                    <div class="bg-slate-950 p-3 rounded text-slate-400 font-mono text-[10px] space-y-1 my-1 border border-slate-800 leading-relaxed">
                        1. Soal nomor satu...<br>
                        A. Pilihan A<br>
                        B. Pilihan B* <span class="text-slate-600">(Kunci)</span><br>
                        C. Pilihan C<br><br>
                        <span class="text-emerald-400 font-bold">&lt;-- Berikan satu baris kosong (Enter) disini --&gt;</span><br><br>
                        2. Soal nomor dua...<br>
                        A. Pilihan A<br>
                        B. Pilihan B
                    </div>
                </div>
                <div class="w-full md:w-auto flex flex-col sm:flex-row gap-2 items-stretch sm:items-center">
                    <input type="file" id="fileTxtSoal" accept=".txt" class="text-[10px] text-slate-400 file:mr-3 file:py-2 file:px-4 file:rounded-xl file:border-0 file:text-[11px] file:font-black file:bg-emerald-600 file:text-white hover:file:bg-emerald-500 cursor-pointer bg-slate-950 border border-slate-800 rounded-xl p-1.5 min-w-[240px]">
                    <button type="button" onclick="prosesUploadTxtSoal()" class="py-2.5 px-5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-black uppercase transition-all whitespace-nowrap shadow-md">
                        ⚙️ Proses File
                    </button>
                </div>
            </div>
        </div>

        <!-- LAYOUT UTAMA -->
        <div class="grid grid-cols-1 md:grid-cols-12 gap-6 items-start">
            <!-- PANEL SETTING WAKTU & INFORMASI (KIRI) -->
            <div class="md:col-span-4 space-y-6">
                <div class="glass-card p-6 rounded-[2rem] space-y-4">
                    <h2 class="text-xs font-bold text-indigo-400 uppercase tracking-wider mb-2">⚙️ Pengaturan Ujian</h2>
                    
                    <div>
                        <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1">Mata Pelajaran</label>
                        <input type="text" id="mapelName" placeholder="Contoh: Matematika" class="w-full p-3 text-sm input-dark rounded-xl font-bold">
                    </div>

                    <div>
                        <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1">Tanggal & Waktu Mulai</label>
                        <input type="datetime-local" id="tglMulai" class="w-full p-3 text-xs input-dark rounded-xl">
                    </div>

                    <div>
                        <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1">Durasi Pengerjaan (Menit)</label>
                        <input type="number" id="durasiMenit" value="60" class="w-full p-3 text-sm input-dark rounded-xl font-semibold">
                    </div>

                    <div class="p-3 bg-slate-900/50 rounded-xl border border-slate-800 flex justify-between items-center">
                        <div>
                            <span class="text-xs font-bold text-white block uppercase">Sistem Anti-Cheat</span>
                            <span class="text-[9px] text-slate-500 block">Kunci tab siswa</span>
                        </div>
                        <label class="relative inline-flex items-center cursor-pointer">
                            <input type="checkbox" id="antiCheatToggle" checked class="sr-only peer">
                            <div class="w-9 h-5 bg-slate-700 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-600"></div>
                        </label>
                    </div>
                </div>

                <div class="glass-card p-6 rounded-[2rem] text-xs text-slate-400 space-y-2">
                    <h3 class="font-bold text-white uppercase text-[10px] text-indigo-400">💡 Petunjuk Singkat:</h3>
                    <p>1. Pilih Jenis Soal terlebih dahulu (**Pilihan Ganda** atau **Essay**).</p>
                    <p>2. Untuk Essay, Anda tidak perlu mengisi opsi jawaban. Cukup tuliskan pertanyaan utamanya.</p>
                    <p>3. Untuk Pilihan Ganda, klik tombol **A, B, C, D, atau E** lalu centang bulatan **Kunci Jawaban**.</p>
                </div>
            </div>

            <!-- PANEL EDITOR DAFTAR BUTIR SOAL (KANAN) -->
            <div class="md:col-span-8 space-y-6">
                <div class="flex justify-between items-center">
                    <h2 class="text-sm font-black text-white uppercase tracking-tight">📝 Daftar Butir Pertanyaan (<span id="totalSoalLabel">0</span>)</h2>
                    <div class="flex gap-2">
                        <button onclick="tambahButirSoalBarunya('PG')" class="bg-emerald-600 hover:bg-emerald-500 text-white px-3 py-2 rounded-xl text-[11px] font-bold uppercase transition-all">➕ PG</button>
                        <button onclick="tambahButirSoalBarunya('ESSAY')" class="bg-blue-600 hover:bg-blue-500 text-white px-3 py-2 rounded-xl text-[11px] font-bold uppercase transition-all">➕ Essay</button>
                    </div>
                </div>

                <div id="containerButirSoal" class="space-y-6"></div>

                <div class="grid grid-cols-2 gap-3 pt-2">
                    <button onclick="tambahButirSoalBarunya('PG')" class="bg-slate-900 hover:bg-slate-800 border border-slate-800 text-emerald-400 p-4 rounded-2xl text-xs font-bold uppercase transition-all">+ Tambah Pilihan Ganda (PG)</button>
                    <button onclick="tambahButirSoalBarunya('ESSAY')" class="bg-slate-900 hover:bg-slate-800 border border-slate-800 text-blue-400 p-4 rounded-2xl text-xs font-bold uppercase transition-all">+ Tambah Soal Essay</button>
                </div>
            </div>
        </div>
    </div>

    <script>
        const API_URL = "api.php";
        const urlParams = new URLSearchParams(window.location.search);
        const currentGuru = urlParams.get('guru') || "";
        const targetMapel = urlParams.get('mapel') || "";
        
        let soalSiap = [];
        let isEditMode = false;
        let activeTabOpsi = []; 

        if (!currentGuru) {
            alert("Sesi guru tidak valid.");
            window.location.href = "guru.php";
        } else {
            document.getElementById('guruDisplay').innerText = "GURU OPERATOR: " + decodeURIComponent(currentGuru);
            initialLoad();
        }

        async function initialLoad() {
            if (targetMapel) {
                isEditMode = true;
                document.getElementById('mapelName').value = decodeURIComponent(targetMapel);
                document.getElementById('mapelName').readOnly = true;
                
                try {
                    const res = await fetch(`${API_URL}?action=get_mapel`).then(r => r.json());
                    const item = res.find(m => m.guru === currentGuru && m.mapel === decodeURIComponent(targetMapel));
                    if (item) {
                        document.getElementById('tglMulai').value = item.waktu ? item.waktu.replace(" ", "T") : "";
                        document.getElementById('durasiMenit').value = item.durasi || 60;
                        document.getElementById('antiCheatToggle').checked = (item.anti_cheat == 1);
                        
                        let soalArray = [];
                        try {
                            soalArray = typeof item.soal === 'string' ? JSON.parse(decodeURIComponent(item.soal)) : item.soal;
                        } catch(e) { soalArray = item.soal; }
                        
                        soalSiap = Array.isArray(soalArray) ? soalArray : [];
                    }
                } catch (e) { console.error(e); }
            } else {
                isEditMode = false;
                tambahButirSoalBarunya('PG');
            }
            
            activeTabOpsi = soalSiap.map(() => 0);
            renderEditor();
        }

        function renderEditor() {
            const container = document.getElementById('containerButirSoal');
            document.getElementById('totalSoalLabel').innerText = soalSiap.length;

            if (soalSiap.length === 0) {
                container.innerHTML = `<div class="glass-card p-12 text-center rounded-3xl text-xs text-slate-500 uppercase">Belum ada butir pertanyaan.</div>`;
                return;
            }

            container.innerHTML = soalSiap.map((s, sIdx) => {
                const cleanTanya = (s.tanya || '').replace(/<br>/g, '\n');
                const currentTabIdx = activeTabOpsi[sIdx] !== undefined ? activeTabOpsi[sIdx] : 0;
                
                // Set default jenis jika belum terekam di DB terdahulu
                if (!s.jenis) {
                    s.jenis = (s.opsi && s.opsi.length > 0 && s.opsi[0].teks !== "") ? "PG" : "ESSAY";
                }

                const isEssay = (s.jenis === 'ESSAY');

                // Generate kerangka opsi jika bertipe PG
                if (!isEssay && (!s.opsi || s.opsi.length < 5)) {
                    s.opsi = Array.from({length: 5}, (_, idx) => ({
                        teks: s.opsi && s.opsi[idx] ? s.opsi[idx].teks : ``,
                        gambar: s.opsi && s.opsi[idx] ? s.opsi[idx].gambar : null,
                        is_correct: s.opsi && s.opsi[idx] ? s.opsi[idx].is_correct : (idx === 0)
                    }));
                }

                const opsiTerpilih = !isEssay ? s.opsi[currentTabIdx] : null;
                const hurufKunciSesuai = (s.kunci_asli || "A").trim().toUpperCase();

                return `
                <div class="glass-card p-6 rounded-3xl border ${isEssay ? 'border-blue-500/30' : 'border-slate-800'} space-y-4 text-left">
                    <div class="flex flex-wrap justify-between items-center bg-slate-950 p-3 rounded-xl border border-slate-800 gap-2">
                        <div class="flex items-center gap-3">
                            <span class="text-xs font-black ${isEssay ? 'text-blue-400' : 'text-indigo-400'} uppercase">Butir Soal #${sIdx+1}</span>
                            <!-- DROPDOWN SINKRONISASI JENIS SOAL -->
                            <select onchange="ubahJenisSoal(${sIdx}, this.value)" class="bg-slate-900 border border-slate-700 rounded-lg text-[11px] font-bold text-white px-2 py-1 outline-none">
                                <option value="PG" ${s.jenis === 'PG' ? 'selected' : ''}>📋 PILIHAN GANDA</option>
                                <option value="減SAY" ${isEssay ? 'selected' : ''} value="ESSAY">📝 ESSAY / ISIAN</option>
                            </select>
                        </div>
                        <div class="flex items-center gap-4">
                            <div class="flex items-center gap-1">
                                <span class="text-[10px] text-slate-500 font-bold">BOBOT:</span>
                                <input type="number" min="0" value="${s.bobot !== undefined ? s.bobot : (isEssay ? 0 : 1)}" onchange="updateDataSoal(${sIdx}, 'bobot', this.value)" class="w-12 bg-slate-900 text-center text-xs font-bold rounded-lg border border-slate-700 text-white py-1">
                            </div>
                            <button onclick="hapusButirSoal(${sIdx})" class="text-xs text-red-400 hover:text-red-500 font-bold uppercase">❌ Hapus</button>
                        </div>
                    </div>

                    ${s.gambar ? `
                    <div class="relative w-fit p-1 bg-slate-950 rounded-xl border border-slate-800">
                        <img src="${s.gambar}" class="max-h-36 rounded-lg">
                        <button onclick="hapusGambarMedia(${sIdx}, 'soal')" class="absolute -top-2 -right-2 bg-red-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold">×</button>
                    </div>` : ''}

                    <div>
                        <textarea onchange="updateDataSoal(${sIdx}, 'tanya', this.value)" placeholder="Tulis teks pertanyaan utama ${isEssay ? 'essay' : ''} di sini..." class="w-full p-4 bg-slate-950 border border-slate-800 rounded-xl text-sm text-white h-32 outline-none resize-y leading-relaxed">${cleanTanya}</textarea>
                        <label class="inline-flex items-center gap-1.5 text-[9px] bg-slate-800 hover:bg-slate-700 px-3 py-1.5 mt-1 rounded-lg cursor-pointer font-bold uppercase text-slate-300">
                            📷 Upload Gambar Soal <input type="file" class="hidden" accept="image/*" onchange="uploadGambarMedia(event, ${sIdx}, 'soal')">
                        </label>
                    </div>

                    <!-- BLOK PILIHAN GANDA: HANYA MUNCUL JIKA BUKAN ESSAY -->
                    ${isEssay ? `
                    <div class="p-4 bg-blue-950/20 rounded-2xl border border-blue-500/20">
                        <span class="text-[10px] text-blue-400 font-extrabold uppercase tracking-wider block">ℹ️ Mode Lembar Jawaban Essay Aktif</span>
                        <p class="text-xs text-slate-400 mt-1">Siswa akan diberikan kolom kosong di lembar ujian untuk mengetik jawaban mereka secara mandiri. Nilai dapat dimasukkan secara manual melalui menu Monitoring Hasil.</p>
                    </div>
                    ` : `
                    <div class="pt-3 border-t border-slate-800/60 space-y-3">
                        <label class="block text-[10px] text-indigo-400 font-bold uppercase tracking-wider">🗂️ Kelola Opsi Jawaban & Kunci (Kunci Saat Ini: ${hurufKunciSesuai}):</label>
                        
                        <div class="flex gap-1.5">
                            ${s.opsi.map((o, oIdx) => {
                                const isTabAktif = (currentTabIdx === oIdx);
                                const hurufKini = String.fromCharCode(65+oIdx);
                                const isKunci = (hurufKini === hurufKunciSesuai);
                                return `
                                <button onclick="gantiTabOpsi(${sIdx}, ${oIdx})" type="button" class="flex-1 py-2 text-center rounded-xl text-xs font-black transition-all ${isTabAktif ? 'bg-indigo-600 text-white ring-2 ring-indigo-400' : 'bg-slate-950 text-slate-400'} border border-slate-800 flex justify-center items-center gap-1">
                                    ${hurufKini} ${isKunci ? '⭐' : ''}
                                </button>
                                `;
                            }).join('')}
                        </div>

                        <div class="p-4 bg-slate-950/80 rounded-2xl border border-indigo-500/20 space-y-3">
                            <div class="flex justify-between items-center">
                                <span class="text-[10px] text-slate-400 font-extrabold uppercase">Mengedit Konten Opsi [ ${String.fromCharCode(65+currentTabIdx)} ] :</span>
                                <label class="flex items-center gap-1.5 text-xs text-emerald-400 font-bold cursor-pointer">
                                    <input type="radio" name="kunci_${sIdx}" ${String.fromCharCode(65+currentTabIdx) === hurufKunciSesuai ? 'checked' : ''} onchange="jadikanKunciJawaban(${sIdx}, ${currentTabIdx})" class="w-4 h-4 accent-emerald-500">
                                    Jadikan Kunci Jawaban Benar
                                </label>
                            </div>

                            ${opsiTerpilih && opsiTerpilih.gambar ? `
                            <div class="relative w-fit p-1 bg-slate-900 rounded-lg border border-slate-800">
                                <img src="${opsiTerpilih.gambar}" class="max-h-24 rounded">
                                <button onclick="hapusGambarMedia(${sIdx}, 'opsi', ${currentTabIdx})" class="absolute -top-1.5 -right-1.5 bg-red-600 text-white text-[10px] rounded-full w-4 h-4 flex items-center justify-center">×</button>
                            </div>` : ''}

                            <input type="text" value="${opsiTerpilih ? (opsiTerpilih.teks || '') : ''}" onchange="updateOpsiTeks(${sIdx}, ${currentTabIdx}, this.value)" placeholder="Tulis isi jawaban untuk Opsi ${String.fromCharCode(65+currentTabIdx)}..." class="w-full p-3 text-xs input-dark rounded-xl">
                            
                            <label class="inline-flex items-center gap-1 text-[9px] bg-slate-800 hover:bg-slate-700 px-2.5 py-1.5 rounded-md cursor-pointer font-bold uppercase text-slate-400">
                                1️⃣ Upload Gambar Opsi ${String.fromCharCode(65+currentTabIdx)}
                                <input type="file" class="hidden" accept="image/*" onchange="uploadGambarMedia(event, ${sIdx}, 'opsi', ${currentTabIdx})">
                            </label>
                        </div>
                    </div>
                    `}
                </div>`;
            }).join('');
        }

        function ubahJenisSoal(sIdx, jenisBaru) {
            soalSiap[sIdx].jenis = jenisBaru;
            if (jenisBaru === 'ESSAY') {
                soalSiap[sIdx].kunci_asli = "-";
                soalSiap[sIdx].opsi = []; // Kosongkan opsi karena bertipe Essay
                soalSiap[sIdx].bobot = 0; // Set standar bobot awal essay adalah 0 (dinilai manual nanti)
            } else {
                soalSiap[sIdx].kunci_asli = "A";
                soalSiap[sIdx].bobot = 1;
            }
            renderEditor();
        }

        function prosesUploadTxtSoal() {
            const fileInput = document.getElementById('fileTxtSoal');
            if (!fileInput.files || fileInput.files.length === 0) {
                return alert("Silakan pilih file format .txt terlebih dahulu!");
            }

            const file = fileInput.files[0];
            const reader = new FileReader();

            reader.onload = function(e) {
                const isiTeks = e.target.result;
                const kumpulanBlokRaw = isiTeks.split(/\n\s*\r?\n/);
                
                let daftarSoalBaru = [];
                const regexNomor = /^\s*(\d+)\.\s*(.*)/; 
                const regexOpsi = /^\s*([A-Ea-e])\.\s*(.*)/;

                kumpulanBlokRaw.forEach((blok) => {
                    const barisDalamBlok = blok.split(/\r?\n/).map(b => b.trim()).filter(b => b.length > 0);
                    if (barisDalamBlok.length === 0) return;

                    let teksPertanyaanKumpul = [];
                    let opsiData = Array.from({length: 5}, (_, idx) => ({
                        teks: "", gambar: null, is_correct: false
                    }));
                    let hurufKunciTerdeteksi = "A";
                    let adaOpsiValid = false;

                    barisDalamBlok.forEach((baris) => {
                        if (regexOpsi.test(baris)) {
                            const matchOpsi = baris.match(regexOpsi);
                            const hurufOpsi = matchOpsi[1].toUpperCase();
                            let teksOpsi = matchOpsi[2];
                            let isKunci = false;

                            if (teksOpsi.endsWith('*')) {
                                isKunci = true;
                                teksOpsi = teksOpsi.slice(0, -1).trim();
                                hurufKunciTerdeteksi = hurufOpsi;
                            }

                            const oIdx = hurufOpsi.charCodeAt(0) - 65; 
                            if (oIdx >= 0 && oIdx < 5) {
                                opsiData[oIdx].teks = teksOpsi;
                                opsiData[oIdx].is_correct = isKunci;
                                adaOpsiValid = true;
                            }
                        } 
                        else if (regexNomor.test(baris)) {
                            const matchNomor = baris.match(regexNomor);
                            teksPertanyaanKumpul.push(matchNomor[2]);
                        } 
                        else {
                            teksPertanyaanKumpul.push(baris);
                        }
                    });

                    if (teksPertanyaanKumpul.length > 0) {
                        // Jika dalam file txt tidak ada format opsi sama sekali, jadikan otomatis ESSAY
                        let tipeDeteksi = adaOpsiValid ? "PG" : "ESSAY";

                        if (tipeDeteksi === "PG") {
                            let cekKunci = opsiData.some(o => o.is_correct);
                            if (!cekKunci && adaOpsiValid) {
                                opsiData[0].is_correct = true;
                                hurufKunciTerdeteksi = "A";
                            }
                            opsiData.forEach((o, idx) => {
                                o.is_correct = (String.fromCharCode(65 + idx) === hurufKunciTerdeteksi);
                            });
                        } else {
                            opsiData = [];
                            hurufKunciTerdeteksi = "-";
                        }

                        daftarSoalBaru.push({
                            id: daftarSoalBaru.length + 1,
                            jenis: tipeDeteksi,
                            tanya: teksPertanyaanKumpul.join('\n'),
                            bobot: tipeDeteksi === "PG" ? 1 : 0,
                            gambar: null,
                            kunci_asli: hurufKunciTerdeteksi,
                            opsi: opsiData
                        });
                    }
                });

                if (daftarSoalBaru.length > 0) {
                    soalSiap = [...soalSiap, ...daftarSoalBaru];
                    soalSiap.forEach((s, i) => s.id = i + 1);
                    activeTabOpsi = soalSiap.map(() => 0);
                    alert(`🎉 Sukses mengimpor ${daftarSoalBaru.length} butir soal.`);
                    renderEditor();
                    fileInput.value = ""; 
                } else {
                    alert("❌ Gagal memproses file. Periksa format teks.");
                }
            };
            reader.readAsText(file);
        }

        function gantiTabOpsi(sIdx, oIdx) {
            activeTabOpsi[sIdx] = oIdx;
            renderEditor();
        }

        function updateDataSoal(idx, field, val) {
            if (field === 'bobot') soalSiap[idx].bobot = parseInt(val) || 0;
            else soalSiap[idx][field] = val;
        }

        function updateOpsiTeks(sIdx, oIdx, val) {
            soalSiap[sIdx].opsi[oIdx].teks = val;
        }

        function jadikanKunciJawaban(sIdx, oIdx) {
            const hurufTerpilih = String.fromCharCode(65 + oIdx);
            soalSiap[sIdx].kunci_asli = hurufTerpilih;
            soalSiap[sIdx].opsi.forEach((o, idx) => {
                o.is_correct = (idx === oIdx);
            });
            renderEditor();
        }

        function tambahButirSoalBarunya(jenisTerpilih = 'PG') {
            soalSiap.push({
                id: soalSiap.length + 1,
                jenis: jenisTerpilih,
                tanya: "",
                bobot: jenisTerpilih === 'PG' ? 1 : 0,
                gambar: null,
                kunci_asli: jenisTerpilih === 'PG' ? "A" : "-",
                opsi: jenisTerpilih === 'PG' ? Array.from({length: 5}, (_, idx) => ({
                    teks: "",
                    gambar: null,
                    is_correct: (idx === 0)
                })) : []
            });
            activeTabOpsi.push(0);
            renderEditor();
        }

        function hapusButirSoal(index) {
            if(confirm("Hapus butir pertanyaan nomor " + (index + 1) + "?")) {
                soalSiap.splice(index, 1);
                activeTabOpsi.splice(index, 1);
                soalSiap.forEach((s, i) => s.id = i + 1);
                renderEditor();
            }
        }

        function hapusGambarMedia(sIdx, tipe, oIdx = null) {
            if (tipe === 'soal') {
                soalSiap[sIdx].gambar = null;
            } else if (tipe === 'opsi') {
                soalSiap[sIdx].opsi[oIdx].gambar = null;
            }
            renderEditor();
        }

        async function uploadGambarMedia(event, sIdx, tipe, oIdx = null) {
            const file = event.target.files[0];
            if (!file) return;
            const fd = new FormData();
            fd.append('gambar', file);
            try {
                const res = await fetch(`${API_URL}?action=upload_gambar`, { method: "POST", body: fd }).then(r => r.json());
                if (res.status === 'success') {
                    if (tipe === 'soal') {
                        soalSiap[sIdx].gambar = res.url;
                    } else if (tipe === 'opsi') {
                        soalSiap[sIdx].opsi[oIdx].gambar = res.url;
                    }
                    renderEditor();
                } else { alert("Gagal mengunggah berkas gambar."); }
            } catch (err) { alert("Sistem upload bermasalah."); }
        }

        async function simpanSeluruhUjian() {
            const mapel = document.getElementById('mapelName').value.trim();
            const waktu = document.getElementById('tglMulai').value;
            const durasi = document.getElementById('durasiMenit').value || 60;
            const antiCheat = document.getElementById('antiCheatToggle').checked ? 1 : 0;

            if(!mapel || !waktu) return alert("Mata Pelajaran dan Waktu Mulai wajib ditentukan!");
            if(soalSiap.length === 0) return alert("Buat minimal 1 soal!");

            // Validasi & Rekonstruksi sinkronisasi data sebelum dilempar JSON ke API / DB
            soalSiap.forEach(s => {
                if (!s.jenis) s.jenis = "PG";
                
                if (s.jenis === 'PG') {
                    if(!s.kunci_asli || s.kunci_asli === "-") s.kunci_asli = "A";
                    if(!s.opsi || s.opsi.length === 0) {
                        s.opsi = Array.from({length: 5}, (_, idx) => ({
                            teks: "", gambar: null, is_correct: (idx === 0)
                        }));
                    }
                    s.opsi.forEach((o, idx) => {
                        o.is_correct = (String.fromCharCode(65 + idx) === s.kunci_asli.trim().toUpperCase());
                    });
                } else {
                    s.kunci_asli = "-";
                    s.opsi = []; // Dipastikan bersih dari array dummy agar api.php membacanya murni ESSAY
                }
            });

            const fd = new FormData();
            fd.append('action', isEditMode ? 'edit_soal' : 'publish_soal');
            fd.append('mapel', mapel);
            fd.append('guru', currentGuru);
            fd.append('waktu', waktu);
            fd.append('durasi', durasi);
            fd.append('anti_cheat', antiCheat);
            fd.append('soal', JSON.stringify(soalSiap));

            try {
                const res = await fetch(API_URL, { method: "POST", body: fd }).then(r => r.json());
                if(res.status === 'success') {
                    alert("Seluruh data ujian (Termasuk Pilihan Ganda & Essay) berhasil disimpan!");
                    kembaliKeDashboard();
                } else { alert("Gagal menyimpan ke DB: " + res.message); }
            } catch(e) { alert("Koneksi gagal."); }
        }

        function kembaliKeDashboard() {
            window.location.href = `guru.php`;
        }
    </script>
</body>
</html>


ANALISIS


<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Analisis Butir Soal - Smart Imtihan</title>
    <!-- Tailwind CSS & Chart.js -->
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #0f172a; color: #f8fafc; }
        .glass-card { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); }
        @media print {
            body { background: white; color: black; }
            .no-print { display: none !important; }
            .glass-card { background: white; border: 1px solid #cbd5e1; box-shadow: none; color: black; page-break-inside: avoid; }
            .text-indigo-400, .text-emerald-400, .text-slate-400 { color: #1e293b !important; }
            canvas { max-width: 100% !important; height: auto !important; }
        }
    </style>
</head>
<body class="min-h-screen p-4 md:p-8">

    <div class="max-w-7xl mx-auto space-y-6">
        
        <!-- HEADER & NAVIGASI -->
        <header class="flex flex-col sm:flex-row justify-between items-center glass-card p-6 rounded-[2rem] gap-4 no-print">
            <div>
                <h1 class="text-xl font-black text-white uppercase tracking-tight">📊 Analisis Butir Soal Statis</h1>
                <p class="text-[11px] text-indigo-400 font-bold uppercase">Evaluasi Validitas & Daya Pembeda Ujian</p>
            </div>
            <div class="flex flex-wrap gap-2 w-full sm:w-auto justify-end">
                <select id="pilihMapel" onchange="muatAnalisisMapel(this.value)" class="p-3 text-xs bg-slate-900 border border-slate-700 rounded-xl font-bold text-white outline-none focus:border-indigo-500">
                    <option value="">-- Pilih Mata Pelajaran --</option>
                </select>
                <button onclick="window.print()" class="bg-indigo-600 hover:bg-indigo-500 text-white px-5 py-3 rounded-xl text-xs font-bold uppercase transition-all shadow-md">🖨️ Cetak / Save PDF</button>
                <button onclick="window.location.href='guru.php'" class="bg-slate-800 hover:bg-slate-700 text-white px-4 py-3 rounded-xl text-xs font-bold uppercase transition-all">Kembali</button>
            </div>
        </header>

        <!-- AREA PRINT ONLY (KOP SURAT SAAT DI-PRINT) -->
        <div class="hidden print:block text-center border-b-4 border-double border-black pb-4 mb-6">
            <h1 class="text-2xl font-black text-black uppercase">MAN 3 KUNINGAN</h1>
            <h2 class="text-md font-bold text-black uppercase tracking-wide">LAPORAN ANALISIS EVALUASI BUTIR SOAL UJIAN</h2>
            <p class="text-xs text-slate-600">Mata Pelajaran: <span id="printMapel" class="font-bold">-</span> | Guru: <span id="printGuru" class="font-bold">-</span></p>
        </div>

        <!-- LAYOUT UTAMA ANALISIS GLOBAL -->
        <div id="dashboardAnalisis" class="hidden space-y-6">
            
            <!-- RINGKASAN STATISTIK -->
            <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
                <div class="glass-card p-5 rounded-2xl">
                    <span class="text-[10px] text-slate-400 block font-bold uppercase">Total Peserta Ujian</span>
                    <span id="statSiswa" class="text-2xl font-black text-white">0</span>
                </div>
                <div class="glass-card p-5 rounded-2xl">
                    <span class="text-[10px] text-slate-400 block font-bold uppercase">Jumlah Butir Soal</span>
                    <span id="statSoal" class="text-2xl font-black text-emerald-400">0</span>
                </div>
                <div class="glass-card p-5 rounded-2xl">
                    <span class="text-[10px] text-slate-400 block font-bold uppercase">Rata-rata Nilai</span>
                    <span id="statRata" class="text-2xl font-black text-indigo-400">0</span>
                </div>
                <div class="glass-card p-5 rounded-2xl">
                    <span class="text-[10px] text-slate-400 block font-bold uppercase">Soal Paling Sulit</span>
                    <span id="statSulit" class="text-2xl font-black text-red-400">No. -</span>
                </div>
            </div>

            <!-- GRAFIK UTAMA (DASHBOARD ATAS) -->
            <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
                <!-- Grafik Batang & Garis Gabungan (Tren Benar per Soal) -->
                <div class="glass-card p-6 rounded-[2rem] lg:col-span-8 space-y-3">
                    <h3 class="text-xs font-bold text-white uppercase tracking-wider">📈 Grafik Turun Naik & Batang: Persentase Keberhasilan per Nomor Soal</h3>
                    <div class="relative h-72 w-full">
                        <canvas id="chartBatangGaris"></canvas>
                    </div>
                </div>

                <!-- Grafik Lingkaran Kontribusi Jawaban Kosong vs Isi -->
                <div class="glass-card p-6 rounded-[2rem] lg:col-span-4 space-y-3 flex flex-col justify-between">
                    <h3 class="text-xs font-bold text-white uppercase tracking-wider text-center">🎯 Grafik Lingkaran: Komposisi Hasil Jawaban</h3>
                    <div class="relative h-56 w-full flex justify-center">
                        <canvas id="chartLingkaranGlobal"></canvas>
                    </div>
                </div>
            </div>

            <!-- BREAKDOWN TIAP BUTIR SOAL (GRAFIK DONAT DETAIL) -->
            <div class="space-y-4">
                <h2 class="text-sm font-black text-white uppercase tracking-wider border-l-4 border-indigo-500 pl-2">🔍 Analisis Distribusi Opsi Tiap Nomor Soal</h2>
                <div id="containerDetailSoal" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
                    <!-- Diisi otomatis via JavaScript -->
                </div>
            </div>

        </div>

        <!-- LOADING / ERROR STATE -->
        <div id="stateKosong" class="glass-card p-12 text-center rounded-[2rem] text-sm text-slate-400 uppercase font-bold">
            Silakan pilih mata pelajaran di atas untuk memuat visualisasi data analisis.
        </div>

    </div>

    <script>
        const API_URL = "api.php";
        let semuaHasil = [];
        let semuaMapel = [];
        let daftarChartInstance = {}; // Menyimpan instance chart agar tidak tumpang tindih saat di-render ulang

        // Palet warna elegan (Tema Midnight Luxury)
        const warnaElegan = {
            hijau: '#10b981', // Emerald Benar
            merah: '#f43f5e',  // Rose Salah
            indigo: '#6366f1', // Indigo A
            biru: '#3b82f6',   // Blue B
            amber: '#f59e0b',  // Amber C
            ungu: '#8b5cf6',   // Purple D
            pink: '#ec4899',   // Pink E
            slate: '#64748b'   // Slate Kosong/Lainnya
        };

        window.onload = async function() {
            try {
                // Tarik data mapel dan hasil ujian
                semuaMapel = await fetch(`${API_URL}?action=get_mapel`).then(r => r.json());
                semuaHasil = await fetch(`${API_URL}?action=get_hasil`).then(r => r.json());

                // Isi Dropdown Opsi Mapel
                const sel = document.getElementById('pilihMapel');
                semuaMapel.forEach(m => {
                    sel.innerHTML += `<option value="${m.mapel}">${m.mapel} [Guru: ${m.guru}]</option>`;
                });
            } catch (e) {
                console.error("Gagal sinkronisasi data analisis: ", e);
            }
        };

        function muatAnalisisMapel(mapelName) {
            if(!mapelName) {
                document.getElementById('dashboardAnalisis').classList.add('hidden');
                document.getElementById('stateKosong').classList.remove('hidden');
                return;
            }

            document.getElementById('dashboardAnalisis').classList.remove('hidden');
            document.getElementById('stateKosong').classList.add('hidden');

            // Filter data hasil khusus untuk mapel ini
            const hasilMapel = semuaHasil.filter(h => h.mapel === mapelName);
            const detailMapel = semuaMapel.find(m => m.mapel === mapelName);

            if(!detailMapel) return alert("Detail konfigurasi naskah soal tidak ditemukan.");

            // Parse struktur soal
            let naskahSoal = [];
            try {
                naskahSoal = typeof detailMapel.soal === 'string' ? JSON.parse(decodeURIComponent(detailMapel.soal)) : detailMapel.soal;
            } catch(e) { naskahSoal = detailMapel.soal; }

            if(!naskahSoal || naskahSoal.length === 0) {
                alert("Tidak ada butir soal dalam mata pelajaran ini.");
                return;
            }

            // Update KOP Cetak & Ringkasan Atas
            document.getElementById('printMapel').innerText = mapelName;
            document.getElementById('printGuru').innerText = detailMapel.guru;
            document.getElementById('statSiswa').innerText = hasilMapel.length;
            document.getElementById('statSoal').innerText = naskahSoal.length;

            let totalNilai = 0;
            hasilMapel.forEach(h => totalNilai += parseFloat(h.skor || 0));
            document.getElementById('statRata').innerText = hasilMapel.length > 0 ? Math.round(totalNilai / hasilMapel.length) : 0;

            // --- PROSES KOMPILASI DATA JAWABAN PER BUTIR SOAL ---
            let labelNomorSoal = [];
            let dataPersenBenar = [];
            let totalJawabanBenarGlobal = 0;
            let totalJawabanSalahGlobal = 0;
            let soalTingkatKesulitan = []; 

            let htmlDetailCard = "";

            naskahSoal.forEach((soal, sIdx) => {
                const noSoal = sIdx + 1;
                labelNomorSoal.push(`No ${noSoal}`);

                // Hitung Kunci Asli Huruf murni
                let kunciMurni = (soal.kunci_asli || "A").trim().toUpperCase();

                // Counter Distribusi Pilihan Opsi
                let cacahOpsi = { A: 0, B: 0, C: 0, D: 0, E: 0, KOSONG: 0 };
                let hitungBenar = 0;
                let hitungSalah = 0;

                hasilMapel.forEach(h => {
                    let detailSiswaArray = [];
                    try { detailSiswaArray = typeof h.detail_hasil === 'string' ? JSON.parse(h.detail_hasil) : h.detail_hasil; } catch(e){}
                    if(!Array.isArray(detailSiswaArray)) detailSiswaArray = [];

                    // Cari log jawaban milik soal nomor ini
                    const itemJwb = detailSiswaArray.find(d => d.id == noSoal);
                    if(itemJwb && itemJwb.jawaban_siswa) {
                        let jwbHuruf = itemJwb.jawaban_siswa.trim().toUpperCase();
                        if(cacahOpsi[jwbHuruf] !== undefined) {
                            cacahOpsi[jwbHuruf]++;
                        } else {
                            cacahOpsi.KOSONG++;
                        }

                        if(itemJwb.hasil === "BENAR" || jwbHuruf === kunciMurni) {
                            hitungBenar++;
                            totalJawabanBenarGlobal++;
                        } else {
                            hitungSalah++;
                            totalJawabanSalahGlobal++;
                        }
                    } else {
                        cacahOpsi.KOSONG++;
                        hitungSalah++;
                        totalJawabanSalahGlobal++;
                    }
                });

                let persenBenar = hasilMapel.length > 0 ? Math.round((hitungBenar / hasilMapel.length) * 100) : 0;
                dataPersenBenar.push(persenBenar);
                soalTingkatKesulitan.push({ no: noSoal, persen: persenBenar });

                // Render Elemen HTML untuk Grafik Donat per Butir Soal
                htmlDetailCard += `
                <div class="glass-card p-4 rounded-2xl flex flex-col justify-between space-y-3">
                    <div class="flex justify-between items-center border-b border-slate-700/50 pb-2">
                        <span class="text-xs font-black text-white">BUTIR SOAL NO. ${noSoal}</span>
                        <span class="text-[10px] bg-slate-800 text-indigo-400 font-extrabold px-2 py-0.5 rounded">Kunci: ${kunciMurni}</span>
                    </div>
                    <p class="text-[11px] text-slate-400 line-clamp-2 italic">"${soal.tanya || 'Tanpa teks'}"</p>
                    <div class="relative h-40 w-full flex justify-center">
                        <canvas id="donut_soal_${noSoal}"></canvas>
                    </div>
                    <div class="grid grid-cols-2 gap-1 text-[10px] font-bold text-center pt-2 border-t border-slate-700/30">
                        <div class="bg-emerald-500/10 text-emerald-400 p-1 rounded">Benar: ${hitungBenar} mhs (${persenBenar}%)</div>
                        <div class="bg-rose-500/10 text-rose-400 p-1 rounded">Salah: ${hitungSalah} mhs</div>
                    </div>
                </div>`;
            });

            document.getElementById('containerDetailSoal').innerHTML = htmlDetailCard;

            // Cari Soal Paling Sulit (Persentase benar terkecil)
            if(soalTingkatKesulitan.length > 0) {
                soalTingkatKesulitan.sort((a,b) => a.persen - b.persen);
                document.getElementById('statSulit').innerText = `No. ${soalTingkatKesulitan[0].no} (${soalTingkatKesulitan[0].persen}%)`;
            }

            // --- DESTRUKSI INSTANCE CHART LAMA AGAR TIDAK LEAK/MEMORI EROR ---
            if(daftarChartInstance['globalBatangGaris']) daftarChartInstance['globalBatangGaris'].destroy();
            if(daftarChartInstance['globalLingkaran']) daftarChartInstance['globalLingkaran'].destroy();

            // 1. RENDER CHART GABUNGAN: BATANG & GARIS (TREN NAIK TURUN)
            const ctxBatangGaris = document.getElementById('chartBatangGaris').getContext('2d');
            daftarChartInstance['globalBatangGaris'] = new Chart(ctxBatangGaris, {
                type: 'bar',
                data: {
                    labels: labelNomorSoal,
                    datasets: [
                        {
                            label: 'Grafik Turun Naik (Tren %)',
                            type: 'line',
                            data: dataPersenBenar,
                            borderColor: warnaElegan.indigo,
                            borderWidth: 3,
                            fill: false,
                            tension: 0.3,
                            pointBackgroundColor: warnaElegan.amber,
                            yAxisID: 'y'
                        },
                        {
                            label: 'Persentase Benar (%)',
                            data: dataPersenBenar,
                            backgroundColor: warnaElegan.hijau + 'A0', // Opacity alpha hex
                            borderRadius: 6,
                            yAxisID: 'y'
                        }
                    ]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: { legend: { labels: { color: '#f8fafc', font: { size: 10, weight: 'bold' } } } },
                    scales: {
                        y: { min: 0, max: 100, ticks: { color: '#64748b', font: { size: 10 } }, grid: { color: 'rgba(255,255,255,0.05)' } },
                        x: { ticks: { color: '#f8fafc', font: { size: 10, weight: 'bold' } }, grid: { display: false } }
                    }
                }
            });

            // 2. RENDER CHART LINGKARAN GLOBAL (KOMPOSISI TOTAL JAWABAN BENAR VS SALAH)
            const ctxLingkaran = document.getElementById('chartLingkaranGlobal').getContext('2d');
            daftarChartInstance['globalLingkaran'] = new Chart(ctxLingkaran, {
                type: 'pie',
                data: {
                    labels: ['Total Benar', 'Total Salah/Kosong'],
                    datasets: [{
                        data: [totalJawabanBenarGlobal, totalJawabanSalahGlobal],
                        backgroundColor: [warnaElegan.hijau, warnaElegan.merah],
                        borderWidth: 0
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: { legend: { position: 'bottom', labels: { color: '#f8fafc', font: { size: 10 } } } }
                }
            });

            // 3. RENDER CHART DONAT DETIL PER BUTIR SOAL (DISTRIBUSI PILIHAN OPSI SISWA)
            naskahSoal.forEach((soal, sIdx) => {
                const noSoal = sIdx + 1;
                let cacahOpsi = { A: 0, B: 0, C: 0, D: 0, E: 0, KOSONG: 0 };

                hasilMapel.forEach(h => {
                    let detailSiswaArray = [];
                    try { detailSiswaArray = typeof h.detail_hasil === 'string' ? JSON.parse(h.detail_hasil) : h.detail_hasil; } catch(e){}
                    if(Array.isArray(detailSiswaArray)){
                        const itemJwb = detailSiswaArray.find(d => d.id == noSoal);
                        if(itemJwb && itemJwb.jawaban_siswa) {
                            let hrf = itemJwb.jawaban_siswa.trim().toUpperCase();
                            if(cacahOpsi[hrf] !== undefined) cacahOpsi[hrf]++;
                            else cacahOpsi.KOSONG++;
                        } else { cacahOpsi.KOSONG++; }
                    } else { cacahOpsi.KOSONG++; }
                });

                const ctxDonut = document.getElementById(`donut_soal_${noSoal}`).getContext('2d');
                
                if(daftarChartInstance[`donut_${noSoal}`]) daftarChartInstance[`donut_${noSoal}`].destroy();

                daftarChartInstance[`donut_${noSoal}`] = new Chart(ctxDonut, {
                    type: 'doughnut',
                    data: {
                        labels: ['Opsi A', 'Opsi B', 'Opsi C', 'Opsi D', 'Opsi E', 'Kosong'],
                        datasets: [{
                            data: [cacahOpsi.A, cacahOpsi.B, cacahOpsi.C, cacahOpsi.D, cacahOpsi.E, cacahOpsi.KOSONG],
                            backgroundColor: [warnaElegan.indigo, warnaElegan.biru, warnaElegan.amber, warnaElegan.ungu, warnaElegan.pink, warnaElegan.slate],
                            borderWidth: 2,
                            borderColor: '#1e293b'
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                position: 'right',
                                labels: { color: '#cbd5e1', font: { size: 9 }, boxWidth: 10 }
                            }
                        },
                        cutout: '65%'
                    }
                });
            });
        }
    </script>
</body>
</html>


ADMIN


<?php
session_start();

// --- 1. KONFIGURASI AKUN LOGIN KHUSUS ---
$akun_admin = [
    "Kepala Sekolah"   => "kepsekman3",
    "Bidang Kurikulum" => "kurikulumman3",
    "Operator Ujian"   => "operatorman3",
    "Admin Web"        => "adminwebman3",
    "BK"               => "bkman3"
];

// Proses Logout jika diminta
if (isset($_GET['logout'])) {
    session_destroy();
    header("Location: admin.php");
    exit;
}

// Proses Validasi Login
$error_login = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action_login'])) {
    $jabatan_input = $_POST['jabatan'] ?? '';
    $password_input = $_POST['password'] ?? '';

    if (isset($akun_admin[$jabatan_input]) && $akun_admin[$jabatan_input] === $password_input) {
        $_SESSION['super_admin_logged'] = true;
        $_SESSION['super_admin_role'] = $jabatan_input;
        header("Location: admin.php");
        exit;
    } else {
        $error_login = "⚠️ Password untuk otoritas " . htmlspecialchars($jabatan_input) . " salah!";
    }
}

// Cek apakah sudah login, jika belum tampilkan Form Login Otoritas Khusus
if (!isset($_SESSION['super_admin_logged']) || $_SESSION['super_admin_logged'] !== true):
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Otorisasi Super Admin - Smart Imtihan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;500;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #020617; }
        .glass-card { background: rgba(15, 23, 42, 0.65); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); }
        .input-dark { background-color: #0f172a; border: 1px solid #334155; color: #ffffff; transition: all 0.3s; }
        .input-dark:focus { border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); }
    </style>
</head>
<body class="min-h-screen flex items-center justify-center p-4 bg-slate-950">
    <div class="w-full max-w-md glass-card p-8 rounded-[2.5rem] border-t-4 border-blue-500 shadow-2xl shadow-blue-950/40">
        <div class="text-center mb-8">
            <div class="w-20 h-20 bg-blue-600/10 border border-blue-500/20 rounded-2xl flex items-center justify-center mx-auto mb-4 p-2">
                <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-full h-full object-contain">
            </div>
            <h1 class="text-2xl font-black text-white uppercase tracking-tight">Gerbang Otoritas Pusat</h1>
            <p class="text-xs text-blue-400 font-bold uppercase tracking-wider mt-1">Smart Imtihan MAN 3 Kuningan</p>               <h4 class="text-xs text-blue-50 font-bold">Created By Mamat Rahmat, Lc</h4>
        </div>

        <?php if(!empty($error_login)): ?>
            <div class="bg-red-500/10 border border-red-500/20 text-red-400 text-xs p-3 rounded-2xl font-bold text-center mb-5">
                <?php echo $error_login; ?>
            </div>
        <?php endif; ?>

        <form method="POST" action="admin.php" class="space-y-5">
            <input type="hidden" name="action_login" value="1">
            
            <div>
                <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1.5 ml-1 tracking-wider">Pilih Jabatan / Otoritas</label>
                <select name="jabatan" class="w-full p-3.5 input-dark rounded-xl text-xs font-bold outline-none cursor-pointer">
                    <option value="Kepala Sekolah">Kepala Sekolah</option>
                    <option value="Bidang Kurikulum">Bidang Kurikulum (Waka)</option>
                    <option value="Operator Ujian">Operator Ujian</option>
                    <option value="Admin Web">Admin Web / IT</option>
                    <option value="BK">Bimbingan Konseling (BK)</option>
                </select>
            </div>

            <div>
                <label class="block text-[10px] text-slate-400 font-bold uppercase mb-1.5 ml-1 tracking-wider">Kunci Keamanan (Password)</label>
                <input type="password" name="password" required placeholder="••••••••" class="w-full p-3.5 input-dark rounded-xl text-xs outline-none tracking-widest font-mono">
            </div>

            <button type="submit" class="w-full py-4 bg-blue-600 hover:bg-blue-500 text-white rounded-xl text-xs font-black uppercase tracking-wider transition-all shadow-lg shadow-blue-600/30 mt-2 active:scale-95">
                🔓 Verifikasi Masuk
            </button>
        </form>
    </div>
</body>
</html>
<?php 
exit; 
endif; 

// --- JIKA SUDAH VERIFIKASI LOGIN, TAMPILKAN DASHBOARD UTAMA DI BAWAH INI ---
?>
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Super Admin Dashboard - Smart Imtihan</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght=400;500;600;700;800&display=swap" rel="stylesheet">
    <style>
        body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #020617; color: #f8fafc; }
        .glass-card { background: rgba(15, 23, 42, 0.65); backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.05); }
        .custom-scroll::-webkit-scrollbar { width: 6px; }
        .custom-scroll::-webkit-scrollbar-thumb { background: #3b82f6; border-radius: 10px; }
    </style>
</head>
<body class="min-h-screen p-4 md:p-8 bg-slate-950">

    <div class="max-w-7xl mx-auto space-y-6">
        
        <header class="flex flex-col md:flex-row justify-between items-center glass-card p-6 rounded-[2rem] gap-4 border-b-2 border-blue-500 shadow-xl shadow-blue-950/20">
            <div class="flex items-center gap-4">
                <div class="p-3 bg-blue-600/10 rounded-2xl border border-blue-500/20">
                    <img src="https://man3-kuningan.my.id/wp-content/uploads/2025/01/logo.png" class="w-12 h-12 object-contain">
                </div>
                <div>
                    <h1 class="text-2xl font-black text-white uppercase tracking-tight">Pusat Kendali Super Admin</h1>
                    <p class="text-xs text-blue-400 font-bold uppercase tracking-wider">
                        Sesi: <span class="text-yellow-400 font-extrabold"><?php echo $_SESSION['super_admin_role']; ?></span> — MAN 3 Kuningan
                    </p>
                </div>
            </div>
            <div class="flex gap-2 w-full md:w-auto justify-end">
                <button onclick="muatDataAdmin()" class="bg-blue-600 hover:bg-blue-500 text-white px-5 py-2.5 rounded-xl text-xs font-black uppercase tracking-wider transition-all shadow-md shadow-blue-600/20">🔄 Segarkan</button>
                <a href="admin.php?logout=1" class="bg-red-500/10 hover:bg-red-600 text-red-500 hover:text-white border border-red-500/20 px-5 py-2.5 rounded-xl text-xs font-black uppercase tracking-wider transition-all">🔒 Keluar</a>
            </div>
        </header>

        <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
            <div class="glass-card p-5 rounded-2xl border-l-4 border-blue-500 shadow-lg">
                <span class="text-[10px] text-slate-400 block font-bold uppercase tracking-widest">Total Mata Pelajaran</span>
                <span id="totalMapel" class="text-3xl font-black text-white mt-1 block">0</span>
            </div>
            <div class="glass-card p-5 rounded-2xl border-l-4 border-emerald-500 shadow-lg">
                <span class="text-[10px] text-slate-400 block font-bold uppercase tracking-widest">Total Ujian Diikuti</span>
                <span id="totalPeserta" class="text-3xl font-black text-emerald-400 mt-1 block">0</span>
            </div>
            <div class="glass-card p-5 rounded-2xl border-l-4 border-indigo-500 shadow-lg">
                <span class="text-[10px] text-slate-400 block font-bold uppercase tracking-widest">Jumlah Guru Aktif</span>
                <span id="totalGuru" class="text-3xl font-black text-indigo-400 mt-1 block">0</span>
            </div>
            <div class="glass-card p-5 rounded-2xl border-l-4 border-red-500 shadow-lg">
                <span class="text-[10px] text-slate-400 block font-bold uppercase tracking-widest">Siswa Terblokir</span>
                <span id="totalBlokir" class="text-3xl font-black text-red-400 mt-1 block">0</span>
            </div>
        </div>

        <div class="glass-card p-6 md:p-8 rounded-[2.5rem] shadow-2xl">
            <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center border-b border-slate-800 pb-4 mb-6 gap-4">
                <div>
                    <h2 class="text-lg font-black text-white uppercase tracking-tight">🗂️ Manajemen & Monitoring Naskah Ujian Guru</h2>
                    <p class="text-xs text-slate-400 mt-0.5">Otoritas kendali penuh multi-user terdeteksi aman</p>
                </div>
                <input type="text" id="cariUjian" oninput="saringTabelUjian(this.value)" placeholder="Cari Guru atau Mata Pelajaran..." class="p-3 bg-slate-900 border border-slate-700 rounded-xl text-xs text-white outline-none focus:border-blue-500 w-full sm:w-64 font-medium transition-all">
            </div>

            <div class="overflow-x-auto custom-scroll">
                <table class="w-full text-left border-collapse min-w-[800px]">
                    <thead>
                        <tr class="text-slate-400 text-[10px] font-bold uppercase border-b border-slate-800 tracking-wider">
                            <th class="pb-4 pl-3">Mata Pelajaran</th>
                            <th class="pb-4">Guru Pengampu</th>
                            <th class="pb-4 text-center">Jumlah Soal</th>
                            <th class="pb-4 text-center">Durasi / Anti-Cheat</th>
                            <th class="pb-4 text-center">Jumlah Pengikut</th>
                            <th class="pb-4 text-right pr-3">Aksi Akses Kontrol</th>
                        </tr>
                    </thead>
                    <tbody id="tabelAdminBody" class="divide-y divide-slate-800/40 text-sm font-medium">
                        <tr>
                            <td colspan="6" class="text-center py-20 text-slate-500 uppercase text-xs font-bold animate-pulse">Menghubungkan ke pusat server database...</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>

    </div>

    <script>
        const API_URL = "api.php";
        let listMapelGlobal = [];
        let listHasilGlobal = [];

        window.onload = muatDataAdmin;

        async function muatDataAdmin() {
            try {
                const [resMapel, resHasil] = await Promise.all([
                    fetch(`${API_URL}?action=get_mapel`).then(r => r.json()),
                    fetch(`${API_URL}?action=get_hasil`).then(r => r.json())
                ]);

                listMapelGlobal = resMapel || [];
                listHasilGlobal = resHasil || [];

                renderStatistikDanTabel();
            } catch (e) {
                console.error("Gagal sinkronisasi data pusat super admin.", e);
                document.getElementById('tabelAdminBody').innerHTML = `<tr><td colspan="6" class="text-center py-10 text-xs text-red-400 uppercase font-black">Gagal memuat repositori data dari server api.php</td></tr>`;
            }
        }

        function renderStatistikDanTabel() {
            const container = document.getElementById('tabelAdminBody');
            
            document.getElementById('totalMapel').innerText = listMapelGlobal.length;
            document.getElementById('totalPeserta').innerText = listHasilGlobal.length;
            
            const kumpulanGuru = new Set(listMapelGlobal.map(m => String(m.guru).trim()));
            document.getElementById('totalGuru').innerText = kumpulanGuru.size;

            const jmlTerblokir = listHasilGlobal.filter(h => (parseInt(h.pelanggaran) || 0) >= 3).length;
            document.getElementById('totalBlokir').innerText = jmlTerblokir;

            if (listMapelGlobal.length === 0) {
                container.innerHTML = `<tr><td colspan="6" class="text-center py-20 text-xs text-slate-500 uppercase font-bold">Belum ada naskah soal ujian yang diterbitkan oleh guru.</td></tr>`;
                return;
            }

            buildDaftarTabel(listMapelGlobal);
        }

        function buildDaftarTabel(dataMapel) {
            const container = document.getElementById('tabelAdminBody');

            container.innerHTML = dataMapel.map(m => {
                const pengikut = listHasilGlobal.filter(h => h.mapel === m.mapel && h.guru_pemilik === m.guru);
                const jmlSoal = Array.isArray(m.soal) ? m.soal.length : 0;

                const paramGuru = encodeURIComponent(m.guru);
                const paramMapel = encodeURIComponent(m.mapel);

                return `
                <tr class="hover:bg-white/[0.02] border-b border-slate-900/50 transition-all text-slate-200">
                    <td class="py-4 pl-3 font-bold text-white uppercase">
                        ${m.mapel}
                        <div class="text-[9px] text-slate-500 font-semibold mt-1 tracking-wider normal-case">Jadwal: ${m.waktu ? m.waktu.replace('T', ' ') : '-'} WIB</div>
                    </td>
                    <td class="text-slate-300 font-semibold uppercase text-xs">${m.guru}</td>
                    <td class="text-center font-mono font-bold text-indigo-400">${jmlSoal} Butir</td>
                    <td class="text-center">
                        <span class="text-xs text-slate-300 font-mono">${m.durasi || 60} Mnt</span>
                        <div class="mt-1">${m.anti_cheat == 1 ? '<span class="text-[8px] bg-red-500/10 text-red-400 border border-red-500/20 font-black px-2 py-0.5 rounded-md uppercase tracking-wider">Anti-Cheat On</span>' : '<span class="text-[8px] bg-slate-800 text-slate-500 font-black px-2 py-0.5 rounded-md uppercase tracking-wider">Off</span>'}</div>
                    </td>
                    <td class="text-center">
                        <span class="text-xs font-black text-emerald-400 bg-emerald-500/10 px-3 py-1 rounded-full border border-emerald-500/20">${pengikut.length} Siswa</span>
                    </td>
                    <td class="text-right pr-3 space-x-1 whitespace-nowrap">
                        <a href="monitor.php?guru=${paramGuru}" target="_blank" class="inline-block bg-slate-900 hover:bg-slate-800 border border-slate-700 text-slate-300 px-3 py-1.5 rounded-xl text-[10px] font-bold uppercase tracking-wide transition-all active:scale-95">
                            🖥️ Monitor
                        </a>
                        <a href="edit.php?guru=${paramGuru}&mapel=${paramMapel}" target="_blank" class="inline-block bg-blue-600 hover:bg-blue-500 text-white px-3 py-1.5 rounded-xl text-[10px] font-bold uppercase tracking-wide transition-all shadow-md shadow-blue-500/10 active:scale-95">
                            ✏️ Edit Soal
                        </a>
                        <a href="analisis.php" target="_blank" class="inline-block bg-indigo-600/20 hover:bg-indigo-600 text-indigo-400 hover:text-white border border-indigo-500/20 px-3 py-1.5 rounded-xl text-[10px] font-bold uppercase tracking-wide transition-all active:scale-95">
                            📊 Analisis
                        </a>
                        <button onclick="adminHapusNaskahUjian('${m.mapel}', '${m.guru}')" class="text-red-500 hover:text-red-400 text-[10px] font-bold uppercase tracking-wide pl-2 hover:underline transition-all">
                            Hapus
                        </button>
                    </td>
                </tr>`;
            }).join('');
        }

        function saringTabelUjian(keyword) {
            const cleanKey = keyword.toLowerCase().trim();
            if(!cleanKey) {
                buildDaftarTabel(listMapelGlobal);
                return;
            }

            const hasilFilter = listMapelGlobal.filter(m => 
                String(m.mapel).toLowerCase().includes(cleanKey) || 
                String(m.guru).toLowerCase().includes(cleanKey)
            );

            buildDaftarTabel(hasilFilter);
        }

        async function adminHapusNaskahUjian(mapel, guru) {
            if (confirm(`⚠️ TINDAKAN SUPER ADMIN!\n\nApakah Anda yakin ingin menghapus permanen bank soal "${mapel}" milik "${guru}"?\nTindakan ini tidak bisa dibatalkan.`)) {
                const fd = new FormData();
                fd.append('action', 'hapus_mapel');
                fd.append('mapel', mapel);
                fd.append('guru', guru);

                try {
                    const res = await fetch(API_URL, { method: "POST", body: fd }).then(r => r.json());
                    if (res.status === 'success') {
                        alert("Naskah soal mapel tersebut berhasil dihapus dari sistem institusi.");
                        muatDataAdmin(); 
                    } else {
                        alert("Gagal menghapus: " + res.message);
                    }
                } catch(e) {
                    alert("Gagal terhubung ke pusat server.");
                }
            }
        }
    </script>
</body>
</html>